130

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

4

6 回答 6

101

由于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 所提到的,您可以使用 move-ctor 而不是 copy-ctor,但这会导致类的不同语义。move-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,
}

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

于 2013-04-16T06:24:19.533 回答
67

在类中拥有 a 的通常情况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
    ~Foo() = default;
    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;
};

这里基本上有两件事:

  • 首先是添加复制和移动构造函数,它们在Foo复制构造函数unique_ptr被删除时被隐式删除。移动构造函数可以简单地通过= default... 添加,这只是为了让编译器知道不应删除通常的移动构造函数这有效,因为unique_ptr已经有一个可以在这种情况下使用的移动构造函数)。

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

  • 如果涉及继承,必须小心地复制原始指针。原因是在上面的代码中进行简单的复制std::unique_ptr<Base>(*ptr)会导致切片,即只有对象的基本组件被复制,而派生部分丢失。

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

于 2017-04-06T18:47:28.890 回答
16

尝试使用此助手创建深层副本,并在源 unique_ptr 为空时应对。

    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;
};
于 2014-11-24T11:50:41.363 回答
6

Daniel Frey 提到了复制解决方案,我会谈谈如何移动 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::move 类似于“标准::右值"

移动后,unique_ptr的资源被转移到另一个unique_ptr

有许多主题记录了右值引用;这是一个很容易开始的

编辑 :

移动的对象应保持有效但未指定的状态

C++ Primer 5, ch13 也很好地解释了如何“移动”对象

于 2013-04-16T06:32:34.063 回答
4

我建议使用 make_unique

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

public:
   A( int i ) : up_(std::make_unique<int>(i)) {}
   A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};

int main()
{
   A a( 42 );
   A b = a;
}
于 2018-07-25T14:13:47.717 回答
0

unique_ptr不可复制,只能移动。

这将直接影响测试,即在您的第二个示例中,它也只能移动而不能复制。

事实上,你使用unique_ptr它来保护你免受大错误是很好的。

例如,您的第一个代码的主要问题是指针永远不会被删除,这非常非常糟糕。说,您可以通过以下方式解决此问题:

class Test
{
    int* ptr; // writing this in one line is meh, not sure if even standard C++

    Test() : ptr(new int(10)) {}
    ~Test() {delete ptr;}
};

int main()
{       
     Test o;
     Test t = o;
}

这也很糟糕。如果你复制会发生什么Test?将有两个类具有指向相同地址的指针。

当一个Test被销毁时,它也会销毁指针。当你的第二个Test被销毁时,它也会尝试删除指针后面的内存。但是它已经被删除了,我们会得到一些糟糕的内存访问运行时错误(或者如果我们不走运,会出现未定义的行为)。

所以,正确的做法是要么实现拷贝构造函数,要么实现拷贝赋值运算符,这样行为就清晰了,我们就可以创建一个拷贝了。

unique_ptr在我们这里遥遥领先。它具有语义:“我是unique,所以你不能只是复制我。 ”因此,它可以防止我们现在实现手头的运算符的错误。

您可以为特殊行为定义复制构造函数和复制赋值运算符,并且您的代码将起作用。但是你,理所当然地(!),被迫这样做。

故事的寓意:总是unique_ptr在这种情况下使用。

于 2019-01-22T16:04:00.507 回答