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

How to approach copying objects with smart pointers as class attributes?

本文关键字:属性 复制 对象 智能 指针      更新时间:2023-10-16

从boost库文档中,我读到了以下内容:

从概念上讲,智能指针被视为拥有所指向的对象,因此,当对象不再是需要。

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

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

我应该搜索可复制智能指针的实现(数据很小,所以我不需要写指针时复制),还是将复制操作委托给我的对象的复制构造函数,如此答案所示?

对于可复制和可赋值的类的简单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;
}

如果您接受对类型的一些要求,则无需为所有类型实现虚拟克隆函数即可实现。特殊的要求是类型具有可访问的复制构造函数,有些人会认为这是不可取的,因为可能会发生意外切片。不过,适当使用交友可能会减轻这种做法的缺点。

如果这是可以接受的,可以通过擦除提供复制功能的接口下的派生类型来实现:

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;
};

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

这有点晚了,但对于未来的观众来说:在我的纯头库Aurora及其SmartPtr教程中有一个现成的实现。使用Aurora,通过智能指针实现深度复制非常简单。以下代码适用于任何可复制类型T:

aurora::CopiedPtr<T> first(new T);
aurora::CopiedPtr<T> second = first; // deep copy

如果你的类有指针成员,那么通常就没有必要实现三巨头/五巨头。

听起来需要制作一个智能指针,以便在每次创建另一个智能指示器对象时创建对象的新副本。(我想,这个副本是否"深层"取决于对象的构造函数;据我们所知,你存储的对象可能有很多层次的所有权,所以"深层"取决于对象的含义。我们的主要目的是,当智能指针是用另一个对象的引用构建的时,你想要的是创建一个不同对象的东西,而不仅仅是取出一个指向现有对象的指针。)

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

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;
};

要使用示例代码,请将其制作成模板等。

我从来没有听说过现成的实现,但您可以自己完成。

首先,您应该编写一些具有虚拟克隆方法的模板包装器类,返回存储对象的副本。然后写一些该类的多面手,这将是可复制的

别忘了选中删除http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete

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

首先,您可以使用std::unique_ptr。这是一个很好的解决方案,因为它强制每个指针有一个实例。(使用std::shared_ptr也可以,但如果不明确添加代码,shared_ptr的复制和赋值将"共享",尤其是您想要避免的内容)。

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

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

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