2013-04-16 26 views
64

如何为具有unique_ptr成员变量的类实现复制构造函数?我只考虑C++ 11。使用unique_ptr复制类的构造函数

+6

好了,你想要什么的拷贝构造函数呢? –

+0

我读过那个unique_ptr是不可复制的。这让我想知道如何在'std :: vector'中使用具有unique_ptr成员变量的类。 – codefx

+0

我认为复制后,如果我们有2个指向同一位置(对象)的唯一指针的对象,那么这是否有意义?它们应该是唯一的。如果一个带指针的类实现拷贝构造函数,那么该指针应该是共享指针,我相信不是一个唯一的指针。 –

回答

53

由于unique_ptr不能共享,你需要或者深复制其内容或转换unique_ptrshared_ptr

class A 
{ 
    std::unique_ptr<int> up_; 

public: 
    A(int i) : up_(new int(i)) {} 
    A(const A& a) : up_(new int(*a.up_)) {} 
}; 

int main() 
{ 
    A a(42); 
    A b = a; 
} 

可以作为NPE提到,使用一招 - 构造函数,而不是一个拷贝构造函数,但是这将导致你的类的不同的语义。一招-ctor的需要,使成员作为移动明确通过std::move

A(A&& a) : up_(std::move(a.up_)) {} 

拥有一整套必要的运营商也导致

A& operator=(const A& a) 
{ 
    up_.reset(new int(*a.up_)); 
    return *this, 
} 

A& operator=(A&& a) 
{ 
    up_ = std::move(a.up_); 
    return *this, 
} 

如果你想使用你的类中std::vector,你基本上必须决定矢量是否是一个对象的唯一所有者,在这种情况下,使这个类可移动但不可复制就足够了。如果省去copy-ctor和copy-assignment,编译器会指导你如何使用带移动类型的std :: vector。

+3

值得一提的移动构造函数? – NPE

+0

@NPE是,已编辑。谢谢! –

+0

@丹尼尔,是不是对于C++ 11来说,我需要遵循四个含义的规则来实现copy-ctor和move-ctor? – codefx

5

丹尼尔·弗雷提及关于复制的解决方案,我就说说如何移动的unique_ptr

#include <memory> 
class A 
{ 
    public: 
    A() : a_(new int(33)) {} 

    A(A &&data) : a_(std::move(data.a_)) 
    { 
    } 

    A& operator=(A &&data) 
    { 
     a_ = std::move(data.a_); 
     return *this; 
    } 

    private: 
    std::unique_ptr<int> a_; 
}; 

他们被称为移动构造函数和移动赋值

你可以使用他们喜欢这个

int main() 
{ 
    A a; 
    A b(std::move(a)); //this will call move constructor, transfer the resource of a to b 

    A c; 
    a = std::move(c); //this will call move assignment, transfer the resource of c to a 

} 

你需要用std :: move来包装a和c,因为他们有一个名字 std :: move正在告诉编译器将值转换为 右值引用任何参数都 在技术意义上说,性病::此举是比喻像“的std ::右值”

移动后,的unique_ptr的资源转移到其他的unique_ptr

有记录右值参考的许多主题; this is a pretty easy one to begin with

编辑:

的移动对象shall remain valid but unspecified state

C++底漆5,也CH13提供有关“移动”的对象

+1

那么在调用'b'移动构造函数中的std :: move(a)之后会发生什么对象'a'?它完全无效吗? –

10

如何试试这个帮手创建深层副本,并应对当源的unique_ptr为null,则很能说明问题。

template< class T > 
    std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source) 
    { 
     return source ? std::make_unique<T>(*source) : nullptr; 
    } 

例如:

class My 
{ 
    My(const My& rhs) 
     : member(copy_unique(rhs.member)) 
    { 
    } 

    // ... other methods 

private: 
    std::unique_ptr<SomeType> member; 
}; 
+0

如果源指向从T派生的东西,它会正确复制吗? –

+0

@RomanShapovalov不,可能不会,你会得到切片。在这种情况下,解决方案可能是为您的T类型添加一个虚拟unique_ptr clone()方法,并在从T派生的类型中提供clone()方法的重写。clone方法将创建派生的新实例键入并返回。 –

+0

在C++或boost库中是否没有内置深度复制功能的唯一/范围指针?如果不需要创建我们的自定义拷贝构造函数等是很好的。对于使用这些智能指针的类,当我们需要深度复制行为时,往往是这种情况。就是想。 –

4

为一体的通常情况下,以具有一个类中的unique_ptr是能够使用继承(否则一个普通的对象会做为好,见RAII)。对于这种情况,目前还没有适当的答案,直到目前为止

所以,这里是起点:

struct Base 
{ 
    //some stuff 
}; 

struct Derived : public Base 
{ 
    //some stuff 
}; 

struct Foo 
{ 
    std::unique_ptr<Base> ptr; //points to Derived or some other derived class 
}; 

...而我们的目标是,正如所说,使Foo可复制的。

为此,需要对包含的指针执行深拷贝以确保派生类被正确复制。

struct Base 
{ 
    //some stuff 

    auto clone() const { return std::unique_ptr<Base>(clone_impl()); } 
protected: 
    virtual Base* clone_impl() const = 0; 
}; 

struct Derived : public Base 
{ 
    //some stuff 

protected: 
    virtual Derived* clone_impl() const override { return new Derived(*this); };             
}; 

struct Foo 
{ 
    std::unique_ptr<Base> ptr; //points to Derived or some other derived class 

    //rule of five, but a user-defined dtor is not necessary due to unique_ptr 
    Foo(Foo const& other) : ptr(other.ptr->clone()) {} 
    Foo(Foo && other) = default; 
    Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; } 
    Foo& operator=(Foo && other) = default; 
}; 

基本上有两件事情会在这里:

这可以通过添加以下代码实现

  • 首先是增加了复制和移动构造函数,由于unique_ptr的复制构造函数被删除,所以在Foo中隐含地删除了它们。此举构造可以简单地通过= default加入...这只是让编译器知道,通常的移动构造函数应被删除(这工作,因为unique_ptr已经有一个移动构造函数可以在这种情况下使用) 。

    对于Foo的拷贝构造函数,没有类似的机制,因为没有unique_ptr的拷贝构造函数。所以,我们必须构建一个新的unique_ptr,用原始指针的副本填充它,并将其用作复制类的成员。

  • 在继承的情况下参与,原来指针对象的副本必须谨慎进行。原因是在上面的代码中通过std::unique_ptr<Base>(*ptr)进行简单的复制将导致切片,即,只有对象的基本组件被复制,而派生部分丢失。

    为了避免这种情况,复印,必须通过克隆模式来完成。这个想法是通过一个虚函数clone_impl()进行复制,它在基类中返回一个Base*。然而,在派生类中,它通过协方差扩展返回Derived*,并且此指针指向派生类的新创建副本。然后基类可以通过基类指针Base*访问这个新对象,将其包装到unique_ptr中,并通过从外部调用的实际clone()函数返回。