The consistent state you are asking for has nothing to do with mutlithreading. Instead, it is an implementation detail of copy-write data classes (even single-threaded) that pass references to internal state.
Consider the String class, which is implemented using CoW (to illustrate, this class cannot be used in stream contexts, since calls to d->refcount not synchronized, it also does not guarantee that the internal char arrary ends with '\0' , and can also eat your grandmother, you were warned):
struct StringRep { StringRep() : capacity(0), size(0), refcount(0), sharable(true), data(0) {} ~StringRep() { delete[] data; } size_t capacity, size, refcount; bool sharable; // later... char * data; }; class String { StringRep * d; public: String() : d(new StringRep) { ++d->refcount; } ~String() { if (--d->refcount <= 0) delete d; } explicit String(const char * s) : d(new StringRep) { ++d->refcount; d->size = d->capacity = strlen(s); d->data = new char[d->size]; memcpy(d->data, s, d->size); } String(const String &other) : d(other.d) { ++d->refcount; } void swap(String &other) { std::swap(d, other.d); } String &operator=(const String &other) { String(other).swap(*this); // copy-swap trick return *this; }
And an example function for mutating and const methods:
void detach() { if (d->refcount == 1) return; StringRep * newRep = new StringRep(*d); ++newRep->refcount; newRep->data = new char[d->size]; memcpy(newRep->data, d->data, d->size); --d->refcount; d = newRep; } void resize(size_t newSize) { if (newSize == d->size) return; detach();
So far so good. But what if we want to provide a mutable operator[] ?
char & operator[](size_t idx) { detach();
This naive implementation has a drawback. Consider the following scenario:
String s1("Hello World!"); char & W = s1[7]; // hold reference to the W assert( W == 'W' ); const String s1(s2); // Shallow copy, but s1, s2 should now // act independently W = 'w'; // modify s1 _only_ (or so we think) assert( W == 'w' ); // ok assert( s1[7] == 'w' ); // ok assert( s2[7] == 'W' ); // boom! s2[7] == 'w' instead!
To prevent this, String should mark itself inseparable when it passes a reference to internal data, so any copy made from it is always deep. So, we need to configure detach() and char & operator[] as follows:
void detach() { if (d->refcount == 1 && /*new*/ d->sharable) return; // rest as above } char & operator[](size_t idx) { detach(); d->shareable = false; // new return d->data[idx]; }
When before reset shareable state return to true again? A common method is to state that references to internal state are invalid when invoking a non-constant method, so where shareable is reset back to true . Since each non-constant function calls detach() , we can reset shareable , so detach() eventually becomes:
void detach() { if (d->refcount == 1 && d->sharable) { d->sharable = true; // new return; } d->sharable = true; // new StringRep * newRep = new StringRep(*d); ++newRep->refcount; newRep->data = new char[d->size+1]; memcpy(newRep->data, d->data, d->size+1); --d->refcount; d = newRep; }