使用unique_ptr复制类的构造函数

cod*_*efx 89 c++ unique-ptr c++11

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

Dan*_*rey 70

由于unique_ptr无法共享,您需要深层复制其内容或将其转换unique_ptr为ashared_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;
}
Run Code Online (Sandbox Code Playgroud)

正如NPE所提到的,你可以使用move-ctor而不是copy-ctor,但这会导致你的类的语义不同.移动控制器需要使该成员明确地通过移动std::move以下方式:

A( A&& a ) : up_( std::move( a.up_ ) ) {}
Run Code Online (Sandbox Code Playgroud)

拥有一套完整的必要操作员也会导致

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

A& operator=( A&& a )
{
   up_ = std::move( a.up_ );
   return *this,
}
Run Code Online (Sandbox Code Playgroud)

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

  • 作为警告,上述策略适用于像`int`这样的简单类型.如果你有一个`unique_ptr <Base>`存储一个`Derived`,上面会切片. (27认同)
  • +1,但应该更加强调移动构造函数.在评论中,OP说目标是在向量中使用对象.为此,移动构造和移动分配是唯一需要的东西. (4认同)
  • 没有检查null,因此as-is允许nullptr取消引用.怎么样的'A(const A&a):up_(a.up_?new int(*a.up_):nullptr){}` (4认同)
  • 可能值得一提的是移动构造函数? (3认同)

dav*_*igh 29

unique_ptr在类中拥有a的通常情况是能够使用继承(否则普通对象通常也会这样做,请参阅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
};
Run Code Online (Sandbox Code Playgroud)

...... 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;
};
Run Code Online (Sandbox Code Playgroud)

这里基本上有两件事:

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

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

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

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

  • 一个人可能正在使用unique_ptr,即使他们知道所指出的具体类型有多种原因:1.它需要可以为空.2.指针对象非常大,我们的堆栈空间可能有限.通常(1)和(2)将一起使用,因此对于可空类型,有时可能更喜欢`unique_ptr`而不是`optional`. (3认同)
  • 疙瘩成语是另一个原因。 (3认同)
  • 这应该是公认的答案.在这个帖子中,其他人都在圈子里,没有暗示为什么人们希望在直接包含时不会复制`unique_ptr`所指向的对象.答案???**遗产**. (2认同)

Sco*_*ham 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;
    }
Run Code Online (Sandbox Code Playgroud)

例如:

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

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};
Run Code Online (Sandbox Code Playgroud)

  • @RomanShapovalov 不,可能不是,你会切片。在这种情况下,解决方案可能是向 T 类型添加一个虚拟的 unique_ptr&lt;T&gt; clone() 方法,并在从 T 派生的类型中提供 clone() 方法的覆盖。 clone 方法将创建一个新实例派生类型并返回。 (4认同)
  • 如果 source 指向从 T 派生的东西,它会正确复制吗? (2认同)

Ste*_*ing 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_;
};
Run Code Online (Sandbox Code Playgroud)

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

你可以像这样使用它们

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

}
Run Code Online (Sandbox Code Playgroud)

你需要用std :: move包装a和c,因为它们有一个名字std :: move告诉编译器将值转换为rvalue引用,无论参数是什么,在技术意义上,std :: move类似于"的std ::右值"

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

记录右值参考的主题很多; 这是一个非常简单的开始.

编辑:

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

C++入门5,ch13也给出了关于如何"移动"对象的非常好的解释


Spl*_*ash 5

我建议使用 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;
}
Run Code Online (Sandbox Code Playgroud)