简体   繁体   English

使用unique_ptr复制类的构造函数

[英]Copy constructor for a class with unique_ptr

How do I implement a copy constructor for a class that has a unique_ptr member variable? 如何为具有unique_ptr成员变量的类实现复制构造函数? I am only considering C++11. 我只考虑C ++ 11。

Since the unique_ptr can not be shared, you need to either deep-copy its content or convert the unique_ptr to a shared_ptr . 由于无法共享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;
}

You can, as NPE mentioned, use a move-ctor instead of a copy-ctor but that would result in different semantics of your class. 正如NPE所提到的,你可以使用move-ctor而不是copy-ctor,但这会导致你的类的语义不同。 A move-ctor would need to make the member as moveable explicitly via std::move : move-ctor需要通过std::move显式地使成员可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. 如果你想在std::vector使用你的类,你基本上必须决定向量是否应该是对象的唯一所有者,在这种情况下,使类可移动,但不可复制就足够了。 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. 如果省略copy-ctor和copy-assignment,编译器将指导你如何使用只移动类型的std :: vector。

The usual case for one to have a unique_ptr in a class is to be able to use inheritance (otherwise a plain object would often do as well, see RAII). 在类中拥有unique_ptr的通常情况是能够使用继承(否则普通对象通常也会这样做,请参阅RAII)。 For this case, there is no appropriate answer in this thread up to now . 对于这种情况, 到目前为止,此线程中没有适当的答案

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 Foo copiable. ......如上所述,目标是使Foo复制。

For this, one needs to do a deep copy of 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 Foo as the copy constructor of unique_ptr is deleted. 第一个是添加复制和移动构造函数,它们在Foo被隐式删除,因为unique_ptr的复制构造函数被删除。 The move constructor can be added simply by = default ... which is just to let the compiler know that the usual move constructor shall not be deleted (this works, as unique_ptr already has a move constructor which can be used in this case). 移动构造函数可以简单地添加= default ...这只是为了让编译器知道通常的移动构造函数不会被删除(这是有效的,因为unique_ptr已经有一个可以在这种情况下使用的移动构造函数)。

    For the copy constructor of Foo , there is no similar mechanism as there is no copy constructor of unique_ptr . 对于Foo的拷贝构造函数,没有类似的机制,因为没有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. 因此,必须构造一个新的unique_ptr ,用原始指针的副本填充它,并将其用作复制类的成员。

  • 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, ie, only the base component of the object gets copied, while the derived part is missing. 原因是在上面的代码中通过std::unique_ptr<Base>(*ptr)进行简单复制会导致切片,即只复制对象的基本组件,而缺少派生的部分。

    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. 我们的想法是通过虚函数clone_impl()进行复制,该函数返回基类中的Base* 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. 但是,在派生类中,通过协方差扩展它以返回Derived* ,并且此指针指向新创建的派生类的副本。 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. 然后,基类可以通过基类指针Base*访问这个新对象,将其包装到unique_ptr ,并通过从外部调用的实际clone()函数返回它。

Try this helper to create deep copies, and cope when the source unique_ptr is null. 尝试使用此帮助程序创建深层副本,并在源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;
    }

Eg: 例如:

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

    // ... other methods

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

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

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也给出了关于如何“移动”对象的非常好的解释

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

unique_ptr is 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. 这将直接影响Test,在第二个示例中,也只是可移动且不可复制的。

In fact, it is good that you use unique_ptr which 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 ? 如果复制Test ,会发生什么? There will be two classes that have a pointer that points to the same address. 将有两个类具有指向同一地址的指针。

When one Test is destroyed, it will also destroy the pointer. 当一个Test被销毁时,它也会破坏指针。 When your second Test is destroyed, it will try to remove the memory behind the pointer, as well. 当您的第二个Test被销毁时,它也会尝试移除指针后面的内存。 But it has already been deleted and we will get some bad memory access runtime error (or undefined behavior if we are unlucky). 但它已被删除,我们将得到一些糟糕的内存访问运行时错误(如果我们运气不好,则会出现未定义的行为)。

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_ptr is way ahead of us here. unique_ptr在我们这里领先于我们。 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 ,所以你不能只复制我。 ”因此,它阻止了我们现在实现手头操作员的错误。

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_ptr in these kind of situations. 故事的寓意:在这种情况下总是使用unique_ptr

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM