dimanche 23 octobre 2016

Is it possible to have a copy construct a class that holds a std::unique_ptr avoiding slicing without Base exposing a "clone" function?

Is there a way to write a copy-constructor for a class (say, Copyable, that holds a std::unique_ptr to a Base class (but really is storing Derived objects.

A quick test shows the expected slicing occurs, because Copyable doesn't know the real type it's holding. So I suppose a clone method is needed, but I'm wondering if there is a way to let the compiler handle this in some better way?

The slicing code:

#include <algorithm>
#include <iostream>
#include <memory>
struct Base
{
  Base(int i = 0) : i(i) {}
  virtual ~Base() = default;
  int i;
  virtual int f() { return i; }
};

struct Derived : Base
{
  Derived() = default;
  virtual int f() override { return 42; }
};

struct Copyable
{
  Copyable(std::unique_ptr<Base>&& base) : data(std::move(base)) {}
  Copyable(const Copyable& other)
  {
    data = std::make_unique<Base>(*other.data);
  }
  std::unique_ptr<Base> data;
};

int main()
{
  Copyable c(std::make_unique<Derived>());
  Copyable c_copy = c;

  std::cout << c_copy.data->f() << '\n';
}

The clone code:

#include <algorithm>
#include <iostream>
#include <memory>
struct Base
{
  Base(int i = 0) : i(i) {}
  virtual ~Base() = default;
  int i;
  virtual int f() { return i; }

  virtual Base* clone() { return new Base(i); }
};

struct Derived : Base
{
  Derived() = default;
  virtual int f() override { return 42; }

  virtual Derived* clone() override { return new Derived(); }

};

struct Copyable
{
  Copyable(std::unique_ptr<Base>&& base) : data(std::move(base)) {}
  Copyable(const Copyable& other)
  {
    data.reset(other.data->clone());
  }
  std::unique_ptr<Base> data;
};

int main()
{
  Copyable c(std::make_unique<Derived>());
  Copyable c_copy = c;

  std::cout << c_copy.data->f() << '\n';
}

Obviously the clone code works. Thing is, there's some things in it I'd like to avoid:

  1. raw new.
  2. a random function that needs to be part of the interface.
  3. This function returns a raw pointer.
  4. Every user of this class that wants to be copyable needs to call this function.

So, is there a "clean" alternative?

Note I want to use smart pointers for all the obvious reasons, I just need a deep copying std::unique_ptr. Something like std::copyable_unique_ptr, combining optional move semantics with a deep copying copy constructor. Is this the cleanest way? Or does that only add the the confusion?

Aucun commentaire:

Enregistrer un commentaire