使用unique_ptr复制类的构造函数


105

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


9
好吧,您希望复制构造函数做什么?
Nicol Bolas

我读到unique_ptr无法复制。这让我想知道如何使用在中具有unique_ptr成员变量的类std::vector
codefx

2
@AbhijitKadam您可以复制unique_ptr内容的深层副本。实际上,这通常是明智的选择。
立方

2
请注意,您可能会问错问题。unique_ptr如果您的目标是将数据放入a中,则可能不希望包含a的类的副本构造函数,而可能需要move构造函数std::vector。另一方面,C ++ 11标准已自动创建了move构造函数,因此也许您确实想要一个复制构造函数……
Yakk-Adam Nevraumont

3
@codefx矢量元素不必是可复制的;这仅意味着该向量将不可复制。
MM

Answers:


81

由于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,
}

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


4
也许值得一提的是移动构造函数?
NPE

4
+1,但应该更加强调move构造函数。OP在评论中说,目标是在矢量中使用对象。为此,仅需要移动构造和移动分配。
jogojapan

36
作为警告,以上策略适用于像的简单类型int。如果您有一个unique_ptr<Base>存储的Derived,则上面的内容将切片。
Yakk-Adam Nevraumont

4
不会检查是否为null,因此按原样可以取消引用nullptr。怎么样A( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
Ryan Haining 2014年

1
@Aaron在多态情况下,删除器将以某种方式删除或毫无意义的类型(如果您知道要删除的类型,为什么只更改删除器?)。无论如何,是的,这是一个value_ptr- unique_ptr加上删除器/复印机信息的设计。
Yakk-Adam Nevraumont

46

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

...的目标是,如所说的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;
};

基本上有两件事正在发生:

  • 第一个是添加了copy和move构造函数,Foo随着的copy构造函数unique_ptr被删除,它们会隐式删除。此举构造可以简单地通过添加= default...这只是为了让编译器知道,通常的移动构造函数将不会被删除(此作品,unique_ptr已经有一个移动构造函数可以在这种情况下使用)。

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

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

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


3
这应该是公认的答案。其他人都在这个线程中盘旋,没有暗示为什么有人希望复制unique_ptr直接包含的对象所指向的对象。答案???继承
Tanveer Badar '18

4
即使他们出于各种原因知道要指向的具体类型,也可能正在使用unique_ptr:1.它必须为可为空。2. Pointee非常大,我们的堆栈空间可能有限。经常有(1)和(2)将一起去,所以有时人们可能更喜欢unique_ptroptional对可空类型。
Ponkadoodle

3
丘疹成语是另一个原因。
emsr

如果基类不应该是抽象的怎么办?如果您忘记了在派生中重新实现它,则不使用纯指示符就可能导致运行时错误。
Oleksij Plotnyc'kyj

1
@ OleksijPlotnyc'kyj:是的,如果您实现clone_implin base,编译器不会告诉您是否在派生类中忘记了它。但是,您可以使用另一个基类Cloneable并在clone_impl那里实现纯虚拟。然后,如果您在派生类中忘记它,编译器将抱怨。
davidhigh

11

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

2
如果源指向T派生的东西,它将正确复制吗?
Roman Shapovalov

3
@RomanShapovalov不,可能不会,您会被切片。在这种情况下,解决方案可能是向您的T类型添加一个虚拟unique_ptr <T> clone()方法,并在派生自T的类型中提供clone()方法的覆盖。clone方法将创建一个新的实例。派生类型并返回该类型。
Scott Langham

c ++或boost库中是否没有内置深度复制功能的唯一/作用域指针?当我们想要深度复制行为时(通常是这种情况),不必为使用这些智能指针的类创建自定义复制构造函数等会很好。就是想。
shadow_map,2016年

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告诉编译器将参数转换为右值引用,无论参数是什么。 std :: rvalue“

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

有很多主题记录右值引用;这是一个很简单的开始

编辑:

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

C ++入门5,ch13还对如何“移动”对象提供了很好的解释


1
那么abmove构造函数中调用std :: move(a)之后对象会发生什么?只是完全无效吗?
David Doria

3

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

-1

unique_ptr 不可复制,只能移动。

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

实际上,最好使用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在这种情况下使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.