Problem |
Improving the performance of an object-oriented program that uniformly
allocates its objects from the heap.
|
Context |
During design, when establishing object scope and lifetimes.
|
Forces |
In the procedural computational model, data lifetimes follow the
lifetime of procedure activation records, which map closely to function
scopes. Because functions are "inside"
objects in object-oriented programs, the data (objects) tend to
outlive the procedures. Yet many C++ programs exhibit a procedural
flow at the use-case level that employs the services of
lower-level objects like strings, lists, sets, and streams. This is
particularly true for programs using C++ as a "better C," and
is true in lesser degree for more advanced C++ programs. Object-oriented programming also brings an additional level of scope not found in procedural programming: the class. The lifetime of many class objects follows the lifetimes of their enclosing objects. For example, the lifetime a string object may be tied to the lifetime of the FileNarne class that contains it; the lifetime of the string object is bounded by the lifetime of the enclosing FileName object. Of course, not all strings are enclosed in FileName objects. A single, uniform memory management strategy makes code easy to understand. One could use operator new as the single, uniform mechanism to create all objects and operator delete to free them. However, these operators are often the performance "hot spots" of strongly object-oriented programs. And memory management leaks from new and delete are the single largest cause of testing bugs in sophisticated C++ programs.
|
Solution |
For those cases where the object lifetime follows the
scope, declare the object as a proper member of the scope. Let the
language invoke the constructors and destructors automatically.
|
Examples |
Not this:
void foo() { SomeClass *x = new SomeClass; .... delete x: }but this: void foo() { SomeCtass x; .... }and not this: class Bar { SomeClass *x; public: Bar() : x(new SomeClass) { .... } ~Bar() { delete x; .... } .... };but this: class Bar { SomeCtass x: public: Bar() : x() { .... ) .... }; |
Resulting context |
There will be reduced memory fragmentation from new and delete,
and the code will run faster because of reduced memory management
overhead. This pattern removes one level of pointer overhead as
well. The program is likely to have less garbage, because more objects
will be properly cleaned up even in the face of exceptions or
user negligence.
|
[Source: James Coplien, "After all, we can't ignore efficiency", C++ Report, May 96, p66] |