Classes RWCString, RWWString, and RWTValVirtualArray<T> use a technique called copy on write to minimize copying. This technique offers the advantage of easy-to-understand value semantics with the speed of reference counted pointer implementation.
Here is how the technique works. When an RWCString is initialized with another RWCString via the copy constructor:
RWCString(const RWCString&);
the two strings share the same data until one of them tries to write to it. At that point, a copy of the data is made and the two strings go their separate ways. Copying only at "write" time makes copies of strings, particularly read-only copies, very inexpensive. In the following example, you can see how four objects share one copy of a string until one of the objects attempts to change the string:
#include <rw/cstring.h> RWCString g; // Global object void setGlobal(RWCString x) { g = x; } main(){ RWCString a("kernel"); // 1 RWCString b(a); // 2 RWCString c(a); // 3 setGlobal(a); // Still only one copy of "kernel"! // 4 b += "s"; // Now b has its own data: "kernels" // 5 }
//1 | The actual allocation and initialization of the memory to hold the string kernel occurs at the RWCString object a. |
//2-//3 | When objects b and c are created from a, they merely increment a reference count in the original data and return. At this point, there are three objects looking at the same piece of data. |
//4 | The function setGlobal() sets the value of g, the global RWCString, to the same value. Now the reference count is up to four, and there is still only one copy of the string kernel. |
//5 | Finally, object b tries to change the value of the string. It looks at the reference count and sees that it is greater than one, implying that the string is being shared by more than one object. At this point, a clone of the string is made and modified. The reference count of the original string drops back down to three, while the reference count of the newly cloned string is one. |
Because copies of RWCStrings are so inexpensive, you are encouraged to store them by value inside your objects, rather than storing a pointer. This will greatly simplify their management, as the following comparison demonstrates. Suppose you have a window whose background and foreground colors can be set. A simple-minded approach to setting the colors would be to use pointers as follows:
class SimpleMinded { const RWCString* foreground; const RWCString* background; public: setForeground(const RWCString* c) {foreground=c;} setBackground(const RWCString* c) {background=c;} };
On the surface, this approach is appealing because only one copy of the string need be made. In this sense, calling setForeground() seems efficient. However, a closer look indicates that the resulting semantics can be muddled: what if the string pointed to by foreground changes? Should the foreground color change? If so, how will class Simple know of the change? There is also a maintenance problem: before you can delete a color string, you must know if anything is still pointing to it.
Here's a much easier approach:
class Smart { RWCString foreground; RWCString background; public: setForeground(const RWCString& c) {foreground=c;} setBackground(const RWCString& c) {background=c;}
Now the assignment foreground=c will use value semantics. The color that class Smart should use is completely unambiguous. Copy on write makes the process efficient, too, since a copy of the data will not be made unless the string should change. The next example maintains a single copy of white until white is changed:
Smart window; RWCString color("white"); window.setForeground(color); // Two references to white color = "Blue"; // One reference to white, one to blue