program story

unique_ptr이있는 클래스의 복사 생성자

inputbox 2020. 9. 10. 07:48
반응형

unique_ptr이있는 클래스의 복사 생성자


unique_ptr멤버 변수 가있는 클래스에 대한 복사 생성자를 어떻게 구현 합니까? 저는 C ++ 11만을 고려하고 있습니다.


이 때문에 unique_ptr공유 할 수 없습니다 중 하나의 내용을 깊은 복사하거나 변환하려면 다음이 필요합니다 unique_ptrA를 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가 언급했듯이 copy-ctor 대신 move-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를 사용하는 방법을 안내합니다.


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복사 가능 하게 만드는 것 입니다.

이를 위해 파생 클래스가 올바르게 복사되도록 포함 된 포인터의 전체 복사 를 수행해야 합니다.

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. 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).

    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.


Try this helper to create deep copies, and cope when the source unique_ptr is 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

#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"

After moving, the resource of the unique_ptr is transfer to another 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


I suggest use 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.

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_ptr which protects you from a big mistake.

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.

When one Test is destroyed, it will also destroy the pointer. When your second Test is 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).

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. 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.

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.

참고URL : https://stackoverflow.com/questions/16030081/copy-constructor-for-a-class-with-unique-ptr

반응형