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::unique_ptr
and 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.
value_wrapper
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.
What’s 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.
An example: ptr_semantics
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 T
, ptr_semantics<T,Alloc>
implements value semantics for pointers to objects allocated by Alloc
. It inherits from default_value_semantics
which implements copy
, move
, copy_assign
, and move_assign
operations in terms of construct
and deref
:
Note we override move
and move_assign
in 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 int
s 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.