如何使用智能指针作为类属性来复制对象?

时间:2012-12-17 10:26:29

标签: c++ smart-pointers copy-constructor assignment-operator

boost library documentation我读到了这个:

  

从概念上讲,智能指针被视为拥有指向的对象,   因此在不再存在时负责删除对象   需要的。

我有一个非常简单的问题:我想使用RAII作为可复制和可分配类的指针属性。

复制和赋值操作应该很深:每个对象都应该有自己的实际数据副本。此外,RTTI需要可用于属性(它们的类型也可以在运行时确定)。

我应该搜索可复制智能指针的实现(数据很小,所以我不需要Copy on Write指针),或者我将复制操作委托给我的对象的副本构造函数显示在this answer

我可以选择哪种智能指针用于可复制和可分配的类的简单RAII? (我认为带有委托复制/赋值操作的unique_ptr到类复制构造函数和赋值运算符会做出正确的选择,但我不确定)

这是使用原始指针的问题的伪代码,它只是一个问题描述,而不是正在运行的C ++代码:

// Operation interface
class ModelOperation
{
    public: 
        virtual void operate = (); 
};

// Implementation of an operation called Special 
class SpecialModelOperation
:
    public ModelOperation
{
    private:
        // Private attributes are present here in a real implementation. 

    public: 

        // Implement operation
        void operate () {}; 
};

// All operations conform to ModelOperation interface
// These are possible operation names: 
// class MoreSpecialOperation; 
// class DifferentOperation; 

// Concrete model with different operations
class MyModel 
{
    private: 
        ModelOperation* firstOperation_; 
        ModelOperation* secondOperation_;  

    public:

        MyModel()
            : 
                firstOperation_(0), 
                secondOperation_(0)
        {
            // Forgetting about run-time type definition from input files here.
            firstOperation_  = new MoreSpecialOperation(); 
            secondOperation_ = new DifferentOperation(); 
        }

        void operate()
        {
            firstOperation_->operate(); 
            secondOperation_->operate();
        }

        ~MyModel() 
        {
            delete firstOperation_; 
            firstOperation_ = 0; 

            delete secondOperation_; 
            secondOperation_ = 0; 
        }
};

int main()
{

    MyModel modelOne; 

    // Some internal scope
    {
        // I want modelTwo to have its own set of copied, not referenced 
        // operations, and at the same time I need RAII to for the operations, 
        // deleting them automatically as soon as it goes out of scope. 
        // This saves me from writing destructors for different concrete models.  
        MyModel modelTwo (modelOne); 
    }


    return 0;
}

5 个答案:

答案 0 :(得分:5)

如果您接受对类型的某些要求,则可以在不需要为所有类型实现虚拟克隆功能的情况下完成此操作。特殊要求是类型具有可访问的拷贝构造器,由于可能的意外切片,一些人认为这些构造器是不合需要的。但是,正确使用友情可以减轻这种缺点。

如果可以接受,可以通过在提供复制功能的界面下删除派生类型来解决此问题:

template <typename Base>
struct clonable {
    // virtual copy
    // this clone function will be generated via templates
    // no boilerplate is involved
    virtual std::unique_ptr<clonable<Base>> clone() const = 0;

    // expose the actual data
    virtual Base* get() = 0;
    virtual Base const* get() const = 0;

    // virtual destructor
    // note that this also obviates the need for a virtual destructor on Base
    // I would probably still make it virtual, though, just in case
    virtual ~clonable() = default;
};

此接口由最衍生类型模板化的类实现,因此知道如何通过复制构造函数制作正常副本。

template <typename Base, typename Derived>
struct clonable_holder : clonable<Base> {
    // I suppose other constructors could be provided
    // like a forwarding one for emplacing, but I am going for minimal here
    clonable_holder(Derived value)
    : storage(std::move(value)) {}

    // here we know the most derived type, so we can use the copy constructor
    // without risk of slicing
    std::unique_ptr<clonable<Base>> clone() const override {
        return std::unique_ptr<clonable<Base>>(new clonable_holder(storage));
    }

    Base* get() override { return &storage; }
    Base const* get() const override { return &storage; }

private:
    Derived storage;
};

这将为我们生成虚拟复制功能,而无需额外的样板。现在我们可以在此基础上构建一个类似智能指针的类(不是一个智能指针,因为它不提供指针语义,而是提供值语义)。

template <typename Base>
struct polymorphic_value {
    // this constructor captures the most derived type and erases it
    // this is a point where slicing may still occur
    // so making it explicit may be desirable
    // we could force constructions through a forwarding factory class for extra safety
    template <typename Derived>
    polymorphic_value(Derived value)
    : handle(new clonable_holder<Base, Derived>(std::move(value))) {
        static_assert(std::is_base_of<Base, Derived>::value,
            "value must be derived from Base");
    }

    // moving is free thanks to unique_ptr
    polymorphic_value(polymorphic_value&&) = default;
    polymorphic_value& operator=(polymorphic_value&&) = default;

    // copying uses our virtual interface
    polymorphic_value(polymorphic_value const& that)
    : handle(that.handle->clone()) {}
    polymorphic_value& operator=(polymorphic_value const& that) {
        handle = that.handle->clone();
        return *this;
    }

    // other useful constructors involve upcasting and so on

    // and then useful stuff for actually using the value
    Base* operator->() { return handle.get(); }
    Base const* operator->() const { return handle.get(); }
    // ...

private:
    std::unique_ptr<clonable<Base>> handle;
};

这只是一个最小的界面,但它可以很容易地从这里充实,以涵盖更多的使用场景。

答案 1 :(得分:4)

现在有点晚了,但对于未来的观众:在我的仅限标题的资源库Aurora及其SmartPtr tutorial中有一个现成的实施方式。使用Aurora,通过智能指针实现深层拷贝是微不足道的。以下代码适用于任何可复制类型(car 1)

T

如果您的类具有指针成员,则通常不必实现The Big Three / Five。

答案 2 :(得分:1)

我从未听说过现成的实现,但你可以自己动手做。

首先你应该写一些模板包装类,它有虚拟克隆方法,返回存储对象的副本。然后写一些可复制的类的多态持有者

并且不要忘记检查删除 http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete

答案 3 :(得分:1)

听起来需要能够创建一个智能指针,每次创建另一个智能指针对象时都会创建对象的新副本。 (我认为,该副本是否“深度”取决于对象的构造函数;对于我们所知道的所有,您存储的对象可能具有很多层次的所有权,因此“深度”取决于对象的含义对象。我们的目的主要是你想要一个创建一个独特对象的东西,当智能指针使用另一个引用构建时,而不是只取出一个指向现有对象的指针。)

如果我已正确理解了这个问题,那么您将需要一个虚拟克隆方法。没有其他方法可以正确调用派生类的构造函数。

struct Clonable {
  virtual ~Clonable() {}
  virtual Clonable* clone() = 0;
};
struct AutoPtrClonable {
  AutoPtrClonable(Clonable* cl=0) : obj(cl) { }
  AutoPtrClonable(const AutoPtrClonable& apc) : obj(apc.obj->clone()) { }
  ~AutoPtrClonable() { delete obj; }
  // operator->, operator*, etc
  Clonable* obj;
};

使用示例代码,将其转换为模板等

答案 4 :(得分:0)

你有两个解决方案(实际上你有更多,但这些对我来说最有意义:)):

首先,您可以使用std::unique_ptr。这是一个很好的解决方案,因为它会强制每个指针有一个实例。 (使用std::shared_ptr代替也可以,但如果你没有明确地添加代码,shared_ptr的复制和分配将“共享” - 特别是你想避免的。)

如果使用std::unique_ptr,则复制构造函数和赋值运算符应显式深层复制(使用指针对象接口中的虚拟clone方法,或者调用{{1}中的新运算符构造函数)。

其次,你可以自己动手。它并没有什么复杂的,我们谈论的是一个小型(10-20行左右)的实用程序类。

就个人而言,如果我必须在一个地方使用这个智能指针类,我会使用std :: unique_ptr。否则(多指针,相同的行为)我会自己滚动,因此我不必为许多实例重复深层复制(以保持DRY原则)。