Before C++11, implementing correct value semantics for a class was a complete pain: You must deal with the constructor, copy constructor, (copy) assignment operator, and then the destructor. Also you had to take some care since those semantics are tightly coupled between that special member functions, so the compiler may reject to provide a default implementation for any of these if you break the semantics of at least one. As you might know, this rule of thumb is known as “The Rule Of Three”.
Since C++11 this is event worse. Now you have move semantics, so there are the three ingredients above plus two more, move constructor and move assignment operator, added to the mix. The Rule Of Three becomes The Rule Of Five in C++11.
Hopefully, the programming world is full of smart and nonconformist guys who always come with a solution. In this case, R. Martinho Fernandes came with the Great Idea of The Rule Of Zero. In short: Only a few classes should deal with resource management, in most of cases relying on such resource handlers for resource ownership is enough and, in that way, default semantics given by the compiler work like a charm.
As the author notices, the point is that the C++ Standard Library already ships handlers for the two most common cases of resource management in C++: Dynamic memory and ownership management, the former through the containers library, the later with smart pointers
std::shared_ptr. We have reach the point that raw
new/delete are not needed any more, even at the point I think they should be deprecated in the long term (But that’s another story…).
Many of us adopted The Rule Of Zero as the main way to do C++ classes, in a way you might never deal with destructors and all that stuff except in some special circumstances (aka tricks ;). But sadly this model is flawed.
Non-RAII to RAII resources: Standard Library version
Consider the most usual case: You love RAII and the magic from destructors and
}, but there’s no handler in the Standard Library to directly deal with your resource; say a handle from a C API. Even fighting with a C API, you expect some RAII sorcery at the C++ side:
“Great, I know about the Rule Of Zero” you may think. Wrap the handle with a
std::shared_ptr and a custom deleter and you are done.
That could be the case for this example, I don’t expect expensive resources like surfaces to be easily copyable but created on demand and shared across the engine. But what if the resource should be copyable? That’s the flaw:
std::shared_ptr does not do value semantics but sharing semantics instead.
That’s the point: From the two alternatives provided by the Standard Library, one is non-copyable, and the other does sharing semantics. There’s a common practice of going ahead and picking
std::shared_ptr just to make my class copy without realizing the semantics that will give to your objects.
The answer from the Standard Library is: Stop programming by Rules and write all the member functions, or waste your time writing a value-semantics adaptor.
I love time wasting.
As usually, I’m not a proficient person at naming things: There’s a
std::reference_wrapper that wraps a reference to make it copyable, and I want to wrap something to make it look like a value. That “simple” reasoning is what my brain did to pick the name.
value_wrapper ? A template that, given a handle type and a description of the required value semantics, builds a wrapper around the handle that makes it look like a value following the semantics.
As you can see, what
value_wrapper is doing is to just bypass the special member function calls to the
Semantics operating on the handle.
Semantics has a member function with a verbose name for each special member function of a class. That’s the way you customize the value semantics, by implementing a policy class with that functions.
Let’s look at a simple example: Pointer semantics. That is, to give value semantics to a dynamically allocated object.
Given an allocator
Alloc and a type
ptr_semantics<T,Alloc> implements value semantics for pointers to objects allocated by
Alloc. It inherits from
default_value_semantics which implements
move_assign operations in terms of
Note we override
ptr_semantics since when working with pointers swapping them is enough. This is applicable to any situation where handles are cheap, but that’s not always the case.
Now we are able to avoid
new/delete while having proper value semantics:
No memory leaks, it copies when the wrapper is copyed, etc.
That’s a fairly non useful example. Who might want to allocate
ints in that way? Well, remember you can pass the allocator you want, so this type can be used to organize dynamic allocation of entities while preserving RAII-correctness and value semantics.
But consider a more interesting example. Given an allocator that stores objects from a class hierarchy, doing the usual task of storing a set of polymorphic widgets is simple:
Unlike when using the Standard Library, that vector is copyable and actually copies the objects it holds.
Note: Writing such
poly_allocator class worths another post just to cover that topic. I leave it as an exercise for the reader. Just a tip: This may inspire you.