unique_ptr
멤버 변수 가있는 클래스에 대한 복사 생성자를 어떻게 구현 합니까? 저는 C ++ 11만을 고려하고 있습니다.
std::vector
.
unique_ptr
원하지 않을 것입니다. 목표가 데이터를 std::vector
. 반면에 C ++ 11 표준은 자동으로 이동 생성자를 생성했기 때문에 복사 생성자를 원할 수도 있습니다.
unique_ptr
멤버 변수 가있는 클래스에 대한 복사 생성자를 어떻게 구현 합니까? 저는 C ++ 11만을 고려하고 있습니다.
std::vector
.
unique_ptr
원하지 않을 것입니다. 목표가 데이터를 std::vector
. 반면에 C ++ 11 표준은 자동으로 이동 생성자를 생성했기 때문에 복사 생성자를 원할 수도 있습니다.
답변:
이 때문에 unique_ptr
공유 할 수 없습니다 중 하나의 내용을 깊은 복사하거나 변환하려면 다음이 필요합니다 unique_ptr
A를 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를 사용하는 방법을 안내합니다.
int
. unique_ptr<Base>
를 저장 하는가있는 경우 Derived
위의 내용이 슬라이스됩니다.
A( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
value_ptr
- unique_ptr
플러스 Deleter가 / 복사기 정보.
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
복사 가능 하게 만드는 것 입니다.
이를 위해 파생 클래스가 올바르게 복사되도록 포함 된 포인터의 전체 복사 를 수행해야 합니다.
다음 코드를 추가하면됩니다.
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;
};
여기에는 기본적으로 두 가지가 있습니다.
첫 번째는 Foo
복사 생성자가 삭제됨에 따라에서 암시 적으로 삭제되는 복사 및 이동 생성자의 추가입니다 unique_ptr
. 이동 생성자는 = default
...로 간단히 추가 할 수 있습니다 . 이는 컴파일러에게 일반적인 이동 생성자 가 삭제 되지 않아야 함 을 알리기위한 것입니다 ( unique_ptr
이 경우에 사용할 수있는 이동 생성자가 이미 있으므로 작동합니다 ).
의 복사 생성자의 경우 복사 생성자 Foo
가없는 것과 유사한 메커니즘이 없습니다 unique_ptr
. 따라서 unique_ptr
새로운를 생성하고 원래 pointee의 복사본으로 채우고 복사 된 클래스의 구성원으로 사용해야합니다.
상속이 포함 된 경우 원본 포인트의 사본을 신중하게 작성해야합니다. 그 이유는 std::unique_ptr<Base>(*ptr)
위의 코드에서 간단한 복사를 수행하면 슬라이스가 발생하기 때문입니다. 즉, 객체의 기본 구성 요소 만 복사되고 파생 된 부분은 누락됩니다.
이를 방지하려면 복제 패턴을 통해 복사해야합니다. 아이디어는 기본 클래스에서 clone_impl()
를 반환하는 가상 함수 를 통해 복사를 수행하는 것입니다 Base*
. 그러나 파생 클래스에서는를 반환하도록 공분산을 통해 확장되며이 Derived*
포인터는 파생 클래스의 새로 생성 된 복사본을 가리 킵니다. 그런 다음 기본 클래스는 기본 클래스 포인터를 통해이 새 객체에 액세스 Base*
하고으로 래핑 하고 외부에서 호출되는 unique_ptr
실제 clone()
함수를 통해 반환 할 수 있습니다.
unique_ptr
직접 격리가 그렇지 않을 때 가리키는 대상을 복사하려는 이유를 암시 하지 않습니다. 대답??? 상속 .
unique_ptr
보다 선호 할 수 optional
있습니다.
clone_impl
in base 를 구현하면 컴파일러는 파생 클래스에서 잊어 버린 경우 알려주지 않습니다. 그러나 다른 기본 클래스를 사용 Cloneable
하고 clone_impl
거기에 순수 가상을 구현할 수 있습니다. 그런 다음 파생 클래스에서 잊어 버리면 컴파일러가 불평합니다.
이 도우미를 사용하여 깊은 복사본을 만들고 소스 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;
};
복사 솔루션에 대한 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
}
a와 c를 std :: move로 래핑해야합니다. std :: move는 매개 변수가 무엇이든간에 값을 rvalue 참조로 변환하도록 컴파일러에 지시합니다. 기술적 의미에서 std :: move는 " std :: rvalue "
이동 후 unique_ptr의 자원은 다른 unique_ptr로 이전됩니다.
rvalue 참조를 문서화하는 많은 주제가 있습니다. 이것은 시작하기 매우 쉬운 것입니다 .
편집하다 :
이동 된 객체 는 유효하지만 지정되지 않은 상태로 유지됩니다 .
C ++ 입문서 5, ch13은 객체를 "이동"하는 방법에 대한 아주 좋은 설명을 제공합니다.
a
에서 std :: move (a)를 호출 한 후 객체는 어떻게됩니까 b
? 완전히 무효입니까?
unique_ptr
복사 할 수 없으며 이동 만 가능합니다.
이것은 테스트에 직접적인 영향을 미치며, 두 번째 예제에서는 이동 만 가능하고 복사 할 수 없습니다.
사실 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
여기서 우리보다 훨씬 앞서 있습니다. 그것은 의미 론적 의미를 가지고 있습니다 : " I am unique
, 그래서 당신은 나를 복사 할 수 없습니다. "그래서, 그것은 우리가 지금 당장 연산자를 구현하는 실수를 방지합니다.
특별한 동작을 위해 복사 생성자와 복사 할당 연산자를 정의 할 수 있으며 코드가 작동합니다. 그러나 당신은 당연히 그렇게 (!) 그렇게해야합니다.
이야기의 교훈 : 항상 unique_ptr
이러한 종류의 상황에서 사용하십시오.