C++ 具有 unique_ptr 的类的复制构造函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16030081/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:01:29  来源:igfitidea点击:

Copy constructor for a class with unique_ptr

c++c++11unique-ptr

提问by codefx

How do I implement a copy constructor for a class that has a unique_ptrmember variable? I am only considering C++11.

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

采纳答案by Daniel Frey

Since the unique_ptrcan not be shared, you need to either deep-copy its content or convert the unique_ptrto a shared_ptr.

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

You can, as NPE mentioned, use a move-ctor instead of a copy-ctor but that would result in different semantics of your class. A move-ctor would need to make the member as moveable explicitly via std::move:

正如 NPE 所提到的,您可以使用移动构造函数而不是复制构造函数,但这会导致类的语义不同。move-ctor 需要通过std::move以下方式显式地使成员成为可移动的:

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

Having a complete set of the necessary operators also leads to

拥有一套完整的必要运算符也会导致

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

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

If you want to use your class in a std::vector, you basically have to decide if the vector shall be the unique owner of an object, in which case it would be sufficient to make the class moveable, but not copyable. If you leave out the copy-ctor and copy-assignment, the compiler will guide your way on how to use a std::vector with move-only types.

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

回答by davidhigh

The usual case for one to have a unique_ptrin a class is to be able to use inheritance (otherwise a plain object would often do as well, see RAII). For this case, there is no appropriate answer in this thread up to now.

unique_ptr在类中拥有 a 的通常情况是能够使用继承(否则普通对象通常也会这样做,请参阅 RAII)。对于这种情况,到目前为止,该线程中没有合适的答案

So, here is the starting point:

所以,这里是起点:

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

... and the goal is, as said, to make Foocopiable.

......正如所说,目标是使可Foo复制。

For this, one needs to do a deep copyof the contained pointer to ensure the derived class is copied correctly.

为此,需要对所包含的指针进行深度复制,以确保正确复制派生类。

This can be accomplished by adding the following code:

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

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

There are basically two things going on here:

这里基本上有两件事:

  • The first is the addition of copy and move constructors, which are implicitly deleted in Fooas the copy constructor of unique_ptris deleted. The move constructor can be added simply by = default... which is just to let the compiler know that the usual move constructor shall notbe deleted (this works, as unique_ptralready has a move constructor which can be used in this case).

    For the copy constructor of Foo, there is no similar mechanism as there is no copy constructor of unique_ptr. So, one has to construct a new unique_ptr, fill it with a copy of the original pointee, and use it as member of the copied class.

  • In case inheritance is involved, the copy of the original pointee must be done carefully. The reason is that doing a simple copy via std::unique_ptr<Base>(*ptr)in the code above would result in slicing, i.e., only the base component of the object gets copied, while the derived part is missing.

    To avoid this, the copy has to be done via the clone-pattern. The idea is to do the copy through a virtual function clone_impl()which returns a Base*in the base class. In the derived class, however, it is extended via covariance to return a Derived*, and this pointer points to a newly created copy of the derived class. The base class can then access this new object via the base class pointer Base*, wrap it into a unique_ptr, and return it via the actual clone()function which is called from the outside.

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

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

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

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

回答by Scott Langham

Try this helper to create deep copies, and cope when the source unique_ptr is null.

试试这个助手来创建深拷贝,并在源 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;
    }

Eg:

例如:

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

    // ... other methods

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

回答by StereoMatching

Daniel Frey mention about copy solution,I would talk about how to move the unique_ptr

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

They are called move constructor and move assignment

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

you could use them like this

你可以像这样使用它们

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

}

You need to wrap a and c by std::move because they have a name std::move is telling the compiler to transform the value to rvalue reference whatever the parameters are In technical sense, std::move is analogy to something like "std::rvalue"

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

After moving, the resource of the unique_ptr is transfer to another unique_ptr

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

There are many topics that document rvalue reference; this is a pretty easy one to begin with.

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

Edit :

编辑 :

The moved object shall remain valid but unspecified state.

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

C++ primer 5, ch13 also give a very good explanation about how to "move" the object

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

回答by Splash

I suggest use make_unique

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

回答by IceFire

unique_ptris not copyable, it is only moveable.

unique_ptr不可复制,只能移动。

This will directly affect Test, which is, in your second, example also only moveable and not copyable.

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

In fact, it is good that you use unique_ptrwhich protects you from a big mistake.

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

For example, the main issue with your first code is that the pointer is never deleted which is really, really bad. Say, you would fix this by:

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

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

This is also bad. What happens, if you copy Test? There will be two classes that have a pointer that points to the same address.

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

When one Testis destroyed, it will also destroy the pointer. When your second Testis destroyed, it will try to remove the memory behind the pointer, as well. But it has already been deleted and we will get some bad memory access runtime error (or undefined behavior if we are unlucky).

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

So, the right way is to either implement copy constructor and copy assignment operator, so that the behavior is clear and we can create a copy.

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

unique_ptris way ahead of us here. It has the semantic meaning: "I am unique, so you cannot just copy me." So, it prevents us from the mistake of now implementing the operators at hand.

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

You can define copy constructor and copy assignment operator for special behavior and your code will work. But you are, rightfully so (!), forced to do that.

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

The moral of the story: always use unique_ptrin these kind of situations.

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