Dalibor Frivaldsky
Dalibor Frivaldsky

Reputation: 850

Returning managed pointer from a clone method

In C++11, two types of "managed" pointer types were introduced - shared_ptr and unique_ptr. Let's now assume we have a set of classes that support a clone() method, such as foo->clone() would return a copy of the foo object. If your goal was to return a managed pointer from the clone() method, how would you allow the user of the interface to select which kind of pointer he wants to be returned?

As a sub-question, would you rather return a raw pointer from the clone() method and let the user construct either shared_ptr or unique_ptr by himself? If not, why?

Upvotes: 2

Views: 576

Answers (2)

tarcisioe
tarcisioe

Reputation: 21

Something along this lines would give you the means to select the kind of smart pointer returned. Would probably be better if encapsulated in a mixin Clonable class template, for maintainability and reusability of the idea.

#include <iostream>
#include <memory>

class Base {
public:
  virtual ~Base() {
    std::cout << "deleting Base\n";
  }

  template <template <typename ...Args> class SmartPtr>
  SmartPtr<Base> clone() {
    return SmartPtr<Base>(this->inner_clone());  
  }

  virtual void speak() const = 0;

private:
  virtual Base *inner_clone() const = 0;
};

class C: public Base {
public:
  ~C() {
    std::cout << "deleting C\n";
  }

  template <template <typename ...Args> class SmartPtr>
  SmartPtr<C> clone() {
    return SmartPtr<C>(this->inner_clone());  
  }

  void speak() const {
    std::cout << "I am C and I inherit from Base!\n";
  }

private:
  C *inner_clone() const override {
    return new C(*this);
  }
};

// End boilerplate.

int main()
{
  auto original = C{};
  // the declarations below should use auto, and are just explicitly typed to
  // show the correct return type of clone();
  std::shared_ptr<C> shared = original.clone<std::shared_ptr>();
  std::unique_ptr<C> unique = original.clone<std::unique_ptr>();

  // the declarations below show it working through conversion to a base class
  // smart pointer type
  std::shared_ptr<Base> sharedBase = original.clone<std::shared_ptr>();
  std::unique_ptr<Base> uniqueBase = original.clone<std::unique_ptr>();

  // the declarations below show it working through the base class for real
  std::shared_ptr<Base> sharedBaseFromBase = sharedBase->clone<std::shared_ptr>();
  std::unique_ptr<Base> uniqueBaseFromBase = uniqueBase->clone<std::unique_ptr>();

  shared->speak();
  unique->speak();
  sharedBase->speak();
  uniqueBase->speak();
  sharedBaseFromBase->speak();
  uniqueBaseFromBase->speak();
}

Compiles with gcc 4.8.1, and should in any compiler supporting variadics.

I would still prefer to simply return a unique_ptr and move the result into a shared_ptr, which would be automatic since the call to clone() is in itself an rvalue.

Upvotes: 0

Kerrek SB
Kerrek SB

Reputation: 477426

The standard smart pointer to manage a dynamic allocation is always unique_ptr. By contrast, shared_ptr is a very specific tool with specialized features (e.g. type-erased deleter, weak pointer observers) and higher costs (virtual dispatch, locked atomic operations) that should only be used when you definitely know you want it. Public raw pointers are a taboo out of principle, and so the natural clone interface looks like this:

struct Base
{
    // must have virtual destructor to destroy through base pointer
    virtual ~Base() {}

    // non-leaf classes are abstract
    virtual std::unique_ptr<Base> clone() const = 0;
};

struct Derived : Base
{
    virtual std::unique_ptr<Base> clone() const override
    {
        return std::unique_ptr<Derived>(new Derived(*this));
        // or "return std::make_unique<Derived>(*this)" in C++14
    }
};

(Unfortunately, we cannot use any kind of covariant return types here, since the template classes unique_ptr<Base> and unique_ptr<Derived> are unrelated. If you prefer to have a clone function that returns the derived type, you could add a non-virtual function like direct_clone that returns a std::unique_ptr<Derived>, and implement the virtual clone() in terms of that.)

Upvotes: 3

Related Questions