Sometimes you need to take decisions and decisions always have consequences.
The story begin with an object like this:
// Foo.h struct Foo { Foo(int v) : val(v) {}; ~Foo() = default; // Copy semantics : OFF Foo(Foo const &) = delete; Foo& operator=(Foo const &) = delete; // Move semantics : ON Foo(Foo &&) = default; Foo& operator=(Foo &&) = default; int val { 7 }; };
Cool, right? The object defines its intention of disallow the copy of itself, forcing the use of move semantics whenever it is possible. And that's the key whenever it is possible:
// main.cpp #include "Foo.h" #include <vector> #include <unordered_map> int main() { // Instances Foo a{1}, b{2}; //b = a; // Nope b = std::move(a); // Yes // On vector std::vector<Foo> v1; v1.emplace_back( Foo(1) ); // Yes // v1.emplace_back( b ); // Nope v1.emplace_back( std::move(b) ); // Yes // std::vector<Foo> v2 { Foo(1), Foo(2), Foo(3) }; // Nope // On unordered_map std::unordered_map<int, Foo> um1; um1.emplace( 0, Foo(0) ); // Yes // std::unordered_map<int, Foo> um2 { {0, Foo(0)}, {1, Foo(1)} }; // Nope }
Some of you may already get the issue. The comfy brace-initializer needs copy-constructor. But beyond that, you could face a situation like this one:
// Bar.h #include "Foo.h" #include <vector> struct Bar { Bar() { for(int i=0; i < 3; ++i) { foos.emplace_back( Foo(i) ); // foos.emplace_back( i ); // Makes the same as above } } std::vector<Foo> foos { }; // Yes // std::vector<Foo> foos { Foo(1), Foo(2) }; // Nope /* * Rule of Zero, so this object should be: * default copyable * default movable * default destructor * BUT: 'foos' contained data is not copyable :( */ };
And now update the main.cpp:
// main.cpp #include "Bar.h" #include <vector> int main() { // Instances Bar a, b; // a = b; // Nope b = std::move(a); // Yes // On vector std::vector<Bar> v1; v1.emplace_back( Bar() ); // Yes // std::vector<Bar> v2 { Bar() }; // Nope }
So the issue here... If you read Bar declaration you may think that it implicitly define copy-semantics, because it is not explicitly deleted.
But as it has a vector of Foo objects and this has explicitly deleted copy-semantics, Bar has implicitly deleted its copy-semantics!
The simplest and more communicative solution is that whenever you need many references to the same Foo object, use a shared_ptr<Foo>
. This gives you a reference counting mechanism out-of-the-box with a name that describe its intention.
To this, suppose the following modification of Bar:
// BarShared.h struct BarShared { BarShared() { for(int i=0; i < 3; ++i) { foos.emplace_back( std::make_shared<Foo>(i) ); // foos.emplace_back( i ); // Same as above but val==0 // foos.emplace_back( i ); // Nope } } std::vector<std::shared_ptr<Foo>> foos; // Yes std::vector<std::shared_ptr<Foo>> foos3 { std::make_shared<Foo>(5), std::make_shared<Foo>(6) }; // Yes /* * Rule of Zero, so this object should be: * default copyable * default movable * default destructor * NOW: 'foos' contained data is copyable :) */ };
The cons of this approach is the use of heap and the mandatory of write std::make_shared<Foo>
.
If you couldn't make use of heap... You should implement the copy-semantics and manage in global scope that ref-counting mechanism, using something like
unordered_map<int,int>
where the key will be an object UUID and value the active references to increment on copy-semantics and decrement on object destructor.
Using that design, relaying the copy responsibility to shared_ptr, we are able to achieve a class that behave as we expect and transmit its intentions.
// main.cpp #include "Bar.h" #include <vector> int main() { // Instances BarShared a, b; a = b; // Yes b = std::move(a); // Yes // On vector std::vector<BarShared> v1; v1.emplace_back( BarShared() ); // Yes std::vector<BarShared> v2 { BarShared() }; // Yes }
We can even add some functions to BarShared
like addFoo(int)
or removeFoo(int)
to manage the internal container and avoid the constant repetition of std::make_shared<Foo>
Code playground: https://www.mycompiler.io/new/cpp?fork=C6I0o7J
That's my first post, so any feedback is welcome ^^
Top comments (0)