I generally like C++ and the way C++ feels, but there is one specific quirk which is caused by STL designers’ choice of data structures which cannot be implemented with a copy-on-write (aka COW) semantic. Curious about which quirk? Keep on reading! COW might look like an implementation detail, but it can really change the way you design your APIs.

COW can sound nasty at first, but it is a good C++ citizen
An object with COW semantic (sometimes also called “implicit sharing”) has an interesting property: whenever you copy it, it does not really do a “deep copy” but simply shares the underlying data between the original object and its copy, and increments a reference counter to track this. Only at the moment that any of the copies is further modified (written to), the deep copy is really performed.
Now, it is important to realize that the overhead imposed by the COW semantic is usually very tiny: the object just needs to bookkeep a counter at every copy, and check it before any modification. The latter is even lighter than it sounds because the compiler can strip off many checks thanks to constant propagations.
But what are the advantages of using COW? The first and foremost advantage is the possibility to have a clean API with respect to return values.
If you have programmed C++ long enough, you know that all C++ functions that should return a complex data structure (anything which is larger than a small structure, like a container, a memory buffer, and so on) will instead accept a reference or a pointer to an object that the caller must provide and that will be “filled in” as return value. This is absolutely awful. If you don’t see it as awful, it’s about time you take a break from C++ and switch to other languages for a while. For instance:
std::vector<Node> nodes;
cur_node.children(nodes);
printf("Number of children: %u\n", nodes.size());
The Node::children() function, in any other language, would have returned a vector of Nodes. But idiomatic C++ says that you should not impose the overhead of doing an additional copy of the vector onto your user, and thus ask him to pass you the container to fill. This is really unreadable because it breaks the common syntax of functions by intermixing return values to arguments. Moreover, it imposes additional troubles to the API; eg: what happens if you call Node::children() with a non-empty vector? Will the function just append its data to the vector? Or would it clear it at the beginning?
Now, think of a world where std::vector is a COW data structure. All these problems suddenly disappear because returning the vector would not cause a copy anymore. Bam, problem solved! And when you have a clear API, where return values really are return values, you get all the benefits from it:
printf("Number of children: %u\n", cur_node.children().size());
Three lines merged into one. No more temporary named variable. And if Node happens to internally store its children as a vector, this code is even faster than the non-COW version, because there is no copy performed at all: you get the same speed as if children() returned a reference to the internal vector, even though it does not.
Other advantages of COW data structures:
vector<vector<int>>. Without COW, whenever the outer data structure resizes, the inner data structures will all be reallocated and copied. With COW, there is no copy at all: the internal data structures are basically moved over in constant time.Nokia’s Qt has designed all objects and data structures with COW. They even made them reentrant (for multithreading purposes) by using atomic reference counting through native CPU instructions. Finally, they expose the guts of COW through a simple QSharedData template that you can use to reimplement your own COW objects.
I could not agree with Qt’s designers more.
This is another example of how COW can positively influence API design:
QByteArray fileHash(QString fn)
{
QFile f(fn);
QCryptographicHash h(QCryptographicHash::Sha1);
while (!f.atEnd())
h.addData(f.read(16*1024));
return h.result();
}
See how I can simply pass the buffer returned by QFile::read() to addData() without having to worry about memory copying.
So, the next time you design an API which asks for a return value among the arguments, think of a C++ world where COW is everywhere. And dream on.
16 Responses
skintension
26|Mar|2010 1I don’t really like code that does non-obvious things behind the scenes. This sort of thing easily leads to confusion and bugs. I suppose it might be more appropriate in some of the more wild-west style languages, but I’d be really hesitant to use something so inexact in c++.
This comment was originally posted on Reddit
giovannibajo
26|Mar|2010 2It’s an optimization, it reorders an operation (deep copy) so that it is performed later in the code flow. It has absolutely zero impact on the semantic of your code. std::vector<>::push_back() reallocs in memory behind the scenes in non predictable moments. Do you avoid that as well in C++?
This comment was originally posted on Reddit
skintension
26|Mar|2010 3Push_back is essentially doing two functions in one – allocating memory if none has been reserved, and then pushing an object. That’s ok, because we already know that’s exactly what it does. It’s completely predictable. No surprised. A COW object is much different. You ask for a copy and you get a reference, which turns into a copy when you perform any number of unrelated operations. *This changes the behavior of those other operations.* When I increment an integer, I expect that 1 is added to the integer and **nothing else**. With a COW object, that behavior has been completely redefined; instead of a simple increment now I’ve got a bunch of memory allocation and copying going on in the background. That could very easily confuse someone, especially someone who is tired or inexperienced. I’m not saying it’s impractical or should be forbidden, but as I originally said, I’d be really hesitant to use something so inexact. It’s not like it’s doing anything you can’t do manually. I’d rather just ask for a reference when I want one, and ask for a copy when I need to modify a copy. If I want a fancy language that does a bunch of behind the scenes hand holding, there’s plenty out there to choose from. If it adds any weight to my words, I’d like to point out that std::string will no longer be allowed to be a COW object in the new C++ standards. That has some significance I think.
This comment was originally posted on Reddit
skintension
26|Mar|2010 4Push_back is essentially doing two functions in one – allocating memory if none has been reserved, and then pushing an object. That’s ok, because we already know that’s exactly what it does. It’s completely predictable. No surprises. A COW object is much different. You ask for a copy and you get a reference, which turns into a copy when you perform any number of unrelated operations. *This changes the behavior of those other operations.* When I increment an integer, I expect that 1 is added to the integer and **nothing else**. With a COW object, that behavior has been completely redefined; instead of a simple increment now I’ve got a bunch of memory allocation and copying going on in the background. That could very easily confuse someone, especially someone who is tired or inexperienced. I’m not saying it’s impractical or should be forbidden, but as I originally said, I’d be really hesitant to use something so inexact. It’s not like it’s doing anything you can’t do manually. I’d rather just ask for a reference when I want one, and ask for a copy when I need to modify a copy. If I want a fancy language that does a bunch of behind the scenes hand holding, there’s plenty out there to choose from. If it adds any weight to my words, I’d like to point out that std::string will no longer be allowed to be a COW object in the new C++ standards. That has some significance I think.
This comment was originally posted on Reddit
mpyne
26|Mar|2010 5> If it adds any weight to my words, I’d like to point out that std::string will no longer be allowed to be a COW object in the new C++ standards. That has some significance I think. The new language will also support move semantics, whereas Qt must run on the old language that does not. And don’t call it "so inexact" either, it’s perfectly exact. If you ever call a function that can modify the shared state then it is detached, if you don’t then it’s not. There are many advanced and confusing concepts, but this (implicit sharing) isn’t one of them. I think you real complaint is merely "This is different!" and *that* is what you’re referring to when you say it is not predictable. It’s not as if the fact that Qt uses implicit sharing is secret or hidden, it’s well-documented in their API reference. For instance, see [QStringList](http://doc.trolltech.com/4.6/qstringlist.htm) For the Qt explanation see [Implicit Sharing](http://doc.trolltech.com/4.6/implicit-sharing.html)
This comment was originally posted on Reddit
skintension
26|Mar|2010 6>If you ever call a function that can modify the shared state then it is detached, if you don’t then it’s not. The list of functions that can modify it is essentially infinite, and you never know if a function will or won’t, unless you’re passing it as const. That’s pretty inexact. If I want to know if a vector is going to realloc when I push_back, I call capacity(). That’s exact. If I want to know if a function is going to cause my COW to alloc and copy… well, I can’t. AGAIN, I’m not saying it’s impractical or should be forbidden, just that it adds complexity in favor of optimization, when that same optimization could be had with a little extra work and no additional complexity. To me that’s not a good trade off – maybe in your line of work it is.
This comment was originally posted on Reddit
mpyne
26|Mar|2010 7> AGAIN, I’m not saying it’s impractical or should be forbidden, just that it adds complexity in favor of optimization, when that same optimization could be had with a little extra work and no additional complexity. Well there is additional complexity, the kind I’m mostly concerned about. That is, complexity for the programmer and not the computer.
I am far, far more likely to screw up writing the program than gcc is to screw up compiling it so it’s in my best interests to write the program in a clear, logical fashion first without making the code look like a & jungle just to be able to pass data around, even if that means having to write a smarter class once beforehand to make it possible. With that said, when I used Qt 3 where explicit sharing was used more often I had occasion at times to have to ensure that the shared data had been detached. The workaround is simple, just call a non-const function that either has no effect or has an effect which is easily reversible. (Qt generally provided manual detach functions for classes that used explicit sharing though). I am looking forward to the next C++ revision if only because move constructors should make it efficient to return data structures by value without requiring implicit sharing, which will make this discussion more interesting.
This comment was originally posted on Reddit
librik
26|Mar|2010 8> I don’t really like code that does non-obvious things behind the scenes. "C++ is the Winchester Mystery House of programming languages." — [Ellen Ullman](http://www.acm.org/ubiquity/interviews/e_ullman_1.html)
This comment was originally posted on Reddit
minimoog77
26|Mar|2010 9http://giovanni.bajo.it/2010/03/c-and-copy-on-write-data-structures/ Норвешки крави или C++ and copy-on-write data structures
This comment was originally posted on Twitter
tek_news
26|Mar|2010 10Reddit/p: C++ and the (Norwegian) COWs http://bit.ly/9lTYYS
This comment was originally posted on Twitter
Inverter
26|Mar|2010 11In C++0x the r-value-references (can) take care of suppressing superfluous copying of return values. We don’t need more COWs, not in a world with more threads!
This comment was originally posted on Reddit
skintension
26|Mar|2010 12> The workaround is simple, just call a non-const function that either has no effect or has an effect which is easily reversible. D:
This comment was originally posted on Reddit
spinwizard69
29|Mar|2010 13A couple of things here. First I have to agree that COW write is a optimization, an optimization that really shouldn’t be built into general purpose libraries. Even worst it is an optimization that many times you really don’t want. Second if you think your code is moving to much data around because of the lack of copy on write then maybe maybe just maybe your code has problems. Third I don’t really see QT as a bench mark example for anything. I know that will irritate people to no end but the duo of QT and KDE has never impressed me on Linux. That may very well be an implementation issue with KDE, but KDE is closely associated with QT and it is a DOG of an environment. Fourth and probably most important, if you are this set against a languages specific feature then don’t use the language. I mean really there are things about Python that get me bent out of shape a bit but I don’t get all excited and post blogs about my disappointment. You either work with the language you got or implement in another better suited. Which highlights one important thought C++ is not the language you would expect to have COW implemented in, it really has no place in a low level language. This thread smacks of an attempt to turn C++ and the STL into something it isn’t.
This comment was originally posted on Reddit
giovannibajo
29|Mar|2010 14> Second if you think your code is moving to much data around because of the lack of copy on write then maybe maybe just maybe your code has problems. The point of the article is that COW allows to design APIs in a way that it would move data around, except that it doesn’t, and it makes the code easier to write and harder to get wrong. And sometimes even faster. I understand that C++0x is going elsewhere, I just don’t agree with that. Qt/KDE is probably the biggest open community around that uses C++, and they are using C++ in a way that it is totally different. And it’s not that it doesn’t work or it sucks in any way, it’s just "different". Better, if you ask me, but YMMV. > Fourth and probably most important, if you are this set against a languages specific feature then don’t use the language. I’m not set against any language feature. The fact that in C++ we are forced to pass return values as arguments is not a language feature per se; it might hint to a *missing* language feature (move semantic), but it is also an artifact of the choice of not using COWs for containers.
This comment was originally posted on Reddit
Alex Karpenko
03|Jun|2010 15“If you have programmed C++ long enough, you know that all C++ functions that should return a complex data structure (anything which is larger than a small structure, like a container, a memory buffer, and so on) will instead accept a reference or a pointer [...]”
Actually, if you’ve programmed C++ long enough, you would know to return by value regardless of the data structure size. Why? Because of a compiler feature known as return value optimization (RVO), which will elide the copy in most cases (and if you are careful, in all cases that matter). Read this: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ to see why it is perfectly fine (and in fact preferred) to return a std::vector, std::vector<std::vector>, std::string etc by value.
In addition, as some people pointed out, this will be even less of an issue than it already is, when C++0x comes around.
Alex
Giovanni Bajo
04|Jun|2010 16@Alex, RVO takes care of many cases, that’s true, though it can’t help when the compiler cannot determine which object is actually returned. Think of a function that constructs two vectors and then decides which one must be returned. You can still exploit RVO by allocating a single return object and swap the selected vector into it, but you’re now playing games instead of coding.
Re: nested vectors, I’m not specifically thinking of return values here. If you push an element in the outer container and cause a resize, all inner containers are reallocated and elements are copied over. This is caused by the missing move semantic.
COW has none of these problems, and it is a good solution today. Move semantic in C++0x will fix performance problems for standard containers, at the cost of making equivalent code even more difficult to write correctly.