Dealing with non-copyable objects - (C++ Tutorial)
Tags: cpp, oop, tutorial, stl
Published: 2021-01-18 08:57:27

Sometimes you need to take decisions and decisions always have consequences.

The story begin with an object like this:

```cpp
// 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:*

```cpp
// 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:

```cpp
// 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***:

```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***:

```cpp
// 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.

```cpp
// 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](https://www.mycompiler.io/new/cpp?fork=C6I0o7J)

*That's my first post, so any feedback is welcome ^^*