|
Problem |
Naive implementations of assignment in C++ are often
inefficient or incorrect.
|
|
Context |
A design has been transformed into body/handle C++
class pairs (Handle/Body pattern). The pattern may be relevant to
other object-based programming languages.
|
|
Forces |
Assignment in C++ is defined recursively as member
by-member assignment with copying as the termination of the recursion;
it would be more efficient and more in the spirit of
Smalltalk if copying were rebinding.
- Copying of bodies is expensive.
- Copying can be avoided by using pointers and references, but
these leave the problem of who is responsible for cleaning up
the object and leave a user-visible distinction between built-in
types and user-defined types.
- Sharing bodies on assignment is usually semantically incorrect
if the shared body is modified through one of the handles.
|
|
Solution |
- A reference count is added to the body class to facilitate
memory management.
- Memory management is added to the handle class, particularly
to its implementation of initialization, assignment, copying,
and destruction.
- It is incumbent on any operation that modifies the state of the
body to break the sharing of the body by making its own copy.
It must decrement the reference count of the original body.
|
|
Forces resolved |
- Gratuitous copying is avoided, leading to a more efficient
implementation.
- Sharing is broken when the body state is modified through any
handle. Sharing is preserved in the more common case of
parameter passing, etc.
- Special pointer and reference types are avoided.
- Smalltalk semantics are approximated; garbage collection is
driven off of this model.
|
|
Design rationale |
Reference counting is efficient and spreads the
overhead across the execution of real-time programs. This
implementation is a variation of shallow copy with the semantics of
deep copy and the efficiency of Smalltalk name-value pairs.
|
|
Example |
class String {
private:
class StringRep {
friend class String;
int count;
char *rep;
StringRep (const char *s) : count(1)
{
strcpy(rep=new char[strlen(s)+1], s);
}
~StringRep() { delete rep; }
} *rep;
public:
String() : rep(new StringRep("")) {}
String(const String &s): rep(s.rep)
{ rep->count++; }
String& operator= (const String &s) {
s.rep->count++;
if(--rep->count <= 0) delete rep;
rep = s.rep;
return *this;
}
~String() {
if (--rep->count <= 0) delete rep;
}
String(const char *s) : rep(newStringRep(s)) {}
....
};
int main() {
String a= "hello", b = "world";
a=b;
return 0;
}
|
|
[Source: James Coplien, "Setting the stage", C++ Report,
Oct 94, p16]
|