使用unique_ptr复制类的构造函数

时间:2013-04-16 06:21:30

标签: c++ c++11 unique-ptr

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

6 个答案:

答案 0 :(得分:66)

由于无法共享unique_ptr,您需要对其内容进行深层复制或将unique_ptr转换为shared_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,
}

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

答案 1 :(得分:22)

在类中拥有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的复制构造函数。因此,必须构造一个新的unique_ptr,用原始指针的副本填充它,并将其用作复制类的成员。

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

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

答案 2 :(得分:10)

尝试使用此帮助程序创建深层副本,并在源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;
};

答案 3 :(得分:5)

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告诉编译器将值转换为 rvalue引用无论参数是什么 从技术意义上讲,std :: move类似于“std :: rvalue”

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

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

编辑:

移动的对象shall remain valid but unspecified state

C ++ primer 5,ch13也给出了关于如何“移动”对象的非常好的解释

答案 4 :(得分:1)

我建议使用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;
}

答案 5 :(得分:-1)

const Timer = process.binding('timer_wrap').Timer; const timeLeft = timer => { timer.timeLeft = function () { if (this._called) return 0; return timer._idleStart + timer._idleTimeout - Timer.now() }; return timer; } const timer = timeLeft(setTimeout(_ => console.log('hi'), 1000)); setInterval(_ => console.log('Time left: ', timer.timeLeft()), 100); 不可复制,只能移动。

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

实际上,使用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