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)