수색…
통사론
-
std::shared_ptr<ClassType> variableName = std::make_shared<ClassType>(arg1, arg2, ...);
-
std::shared_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
-
std::unique_ptr<ClassType> variableName = std::make_unique<ClassType>(arg1, arg2, ...);
// C ++ 14 -
std::unique_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
비고
C ++은 메모리 관리 언어가 아닙니다. 동적으로 (로 만든 즉, 객체 메모리를 할당 new
명시 적으로 (로 할당이 해제되지 않은 경우 "유출"됩니다) delete
). 동적으로 할당 된 메모리가 그 객체에 대한 마지막 포인터를 버리기 전에 해제되는 것을 보장하는 것은 프로그래머의 책임입니다.
스마트 포인터를 사용하여 동적으로 할당 된 메모리의 범위를 자동으로 관리 할 수 있습니다 (예 : 마지막 포인터 참조가 범위를 벗어나 삭제 된 경우).
스마트 포인터는 대부분 "원시"포인터보다 선호됩니다. 동적으로 할당 된 메모리의 소유권 의미를 명시 적으로 나타내며, 객체가 공유되거나 고유하게 소유되도록 의도되었는지 여부를 그들의 이름으로 전달합니다.
스마트 포인터를 사용하려면 #include <memory>
를 사용하십시오.
소유권 공유 (std :: shared_ptr)
클래스 템플릿 std::shared_ptr
은 객체의 소유권을 다른 공유 포인터와 공유 할 수있는 공유 포인터를 정의합니다. 이는 독점권을 나타내는 std::unique_ptr
과 대조됩니다.
공유 동작은 개체를 가리키는 공유 포인터의 수와 함께 참조 계산으로 알려진 기술을 통해 구현됩니다. 마지막 std::shared_ptr
인스턴스의 삭제 또는 재 할당을 통해이 수가 0에 도달하면 객체가 자동으로 삭제됩니다.
// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);
동일한 객체를 공유하는 여러 스마트 포인터를 만들려면 첫 번째 공유 포인터의 별칭 인 다른 shared_ptr
을 만들어야합니다. 다음과 같은 두 가지 방법이 있습니다.
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
위의 방법 중 하나는 secondShared
를 firstShared
와 Foo
인스턴스의 소유권을 공유하는 공유 포인터로 firstShared
.
스마트 포인터는 원시 포인터처럼 작동합니다. 즉, *
를 사용하여 참조를 취소 할 수 있습니다. 일반 연산자 ->
연산자도 사용할 수 있습니다.
secondShared->test(); // Calls Foo::test()
마지막으로 별명이 지정된 shared_ptr
이 범위를 벗어나면 Foo
인스턴스의 소멸자가 호출됩니다.
경고 : 공유 소유권 의미에 대한 추가 데이터를 할당해야하는 경우 shared_ptr
하면 bad_alloc
예외가 발생할 수 있습니다. 생성자에 일반 포인터가 전달되면 지정된 객체를 소유하고 있다고 가정하고 예외가 throw되면 deleter를 호출합니다. 이것은 shared_ptr<T>(new T(args))
가 shared_ptr<T>
할당이 실패 할 경우 T
객체를 누설하지 않는다는 것을 의미합니다. 그러나 make_shared<T>(args)
또는 allocate_shared<T>(alloc, args)
를 사용하여 메모리 할당을 최적화하는 것이 좋습니다.
shared_ptr을 사용하여 배열 ([]) 할당
불행하게도, make_shared<>
사용하여 배열을 할당하는 직접적인 방법은 없습니다.
new
및 std::default_delete
사용하여 shared_ptr<>
대한 배열을 만들 수 있습니다.
예를 들어 10 개의 정수 배열을 할당하려면 다음과 같이 코드를 작성할 수 있습니다.
shared_ptr<int> sh(new int[10], std::default_delete<int[]>());
std::default_delete
지정하면 할당 된 메모리가 delete[]
사용하여 올바르게 정리되었는지 확인해야합니다.
컴파일 시간에 크기를 알면 다음과 같이 할 수 있습니다.
template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
std::shared_ptr<T> operator()const{
auto r = std::make_shared<std::array<T,N>>();
if (!r) return {};
return {r.data(), r};
}
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }
make_shared_array<int[10]>
는 10 개의 정수를 가리키는 shared_ptr<int>
반환한다.
공유 포인터는 자신이 소유 한 객체의 하위 객체를 가리킬 수 있습니다.
struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);
p2
와 p1
모두 Foo
유형의 객체를 소유하지만 p2
는 int
멤버 x
가리 킵니다. 즉, p1
이 범위를 벗어나거나 재 할당되는 경우 기본 Foo
객체가 여전히 작동하여 p2
가 매달리지 않도록합니다.
중요 : shared_ptr
은 별칭 생성자로 만든 자체 및 모든 다른 shared_ptr
에 대해서만 알고 있습니다. 동일한 Foo
인스턴스에 대한 참조로 생성 된 다른 모든 shared_ptr
포함하여 다른 포인터에 대해서는 알지 못합니다.
Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this
shared1.reset(); // this will delete foo, since shared1
// was the only shared_ptr that owned it
shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
// deleted already!!
shared_ptr의 소유권 이전
기본적으로 shared_ptr
은 참조 횟수를 증가시키고 소유권을 이전하지 않습니다. 그러나 std::move
사용하여 소유권을 이전 할 수 있습니다.
shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1
임시 소유권으로 공유 (std :: weak_ptr)
std::weak_ptr
인스턴스는 std::shared_ptr
인스턴스가 소유 한 객체를 가리킬 수 있지만 일시적인 소유자 만이 될 수 있습니다. 즉, 약한 포인터는 개체의 참조 횟수를 변경하지 않으므로 모든 개체의 공유 포인터가 다시 할당되거나 파괴되면 개체의 삭제를 방지하지 못합니다.
다음 예제에서 std::weak_ptr
인스턴스는 트리 객체의 삭제가 금지되지 않도록 사용됩니다.
#include <memory>
#include <vector>
struct TreeNode {
std::weak_ptr<TreeNode> parent;
std::vector< std::shared_ptr<TreeNode> > children;
};
int main() {
// Create a TreeNode to serve as the root/parent.
std::shared_ptr<TreeNode> root(new TreeNode);
// Give the parent 100 child nodes.
for (size_t i = 0; i < 100; ++i) {
std::shared_ptr<TreeNode> child(new TreeNode);
root->children.push_back(child);
child->parent = root;
}
// Reset the root shared pointer, destroying the root object, and
// subsequently its child nodes.
root.reset();
}
자식 노드가 루트 노드의 자식 노드에 추가 될 때 std::weak_ptr
멤버 parent
는 루트 노드로 설정됩니다. 멤버 parent
는 루트 포인터의 참조 횟수가 증가하지 않도록 공유 포인터와 달리 약 포인터로 선언됩니다. 루트 노드가 main()
의 끝에서 재설정되면 루트가 삭제됩니다. 유일하게 남아 있기 때문에 std::shared_ptr
자식 노드에 대한 참조가 루트의 수집에 포함 된 children
, 모든 자식 노드 이후도 파괴된다.
제어 블록 구현 세부 사항으로 인해 shared_ptr
참조 카운터와 weak_ptr
참조 카운터가 모두 0에 도달 할 때까지 shared_ptr
할당 메모리가 해제되지 않을 수 있습니다.
#include <memory>
int main()
{
{
std::weak_ptr<int> wk;
{
// std::make_shared is optimized by allocating only once
// while std::shared_ptr<int>(new int(42)) allocates twice.
// Drawback of std::make_shared is that control block is tied to our integer
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// sh memory should be released at this point...
}
// ... but wk is still alive and needs access to control block
}
// now memory is released (sh and wk)
}
이후 std::weak_ptr
살아 통해 직접 데이터 액세스 참조 된 개체를 유지하지 않습니다 std::weak_ptr
불가능합니다. 대신 참조 된 객체에 대해 std::shared_ptr
을 검색하려고 시도하는 lock()
멤버 함수를 제공합니다.
#include <cassert>
#include <memory>
int main()
{
{
std::weak_ptr<int> wk;
std::shared_ptr<int> sp;
{
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// calling lock will create a shared_ptr to the object referenced by wk
sp = wk.lock();
// sh will be destroyed after this point, but sp is still alive
}
// sp still keeps the data alive.
// At this point we could even call lock() again
// to retrieve another shared_ptr to the same data from wk
assert(*sp == 42);
assert(!wk.expired());
// resetting sp will delete the data,
// as it is currently the last shared_ptr with ownership
sp.reset();
// attempting to lock wk now will return an empty shared_ptr,
// as the data has already been deleted
sp = wk.lock();
assert(!sp);
assert(wk.expired());
}
}
고유 소유권 (std :: unique_ptr)
std::unique_ptr
은 동적으로 저장된 객체의 수명을 관리하는 클래스 템플릿입니다. std::shared_ptr
과는 달리, 동적 객체는 언제든지 std::unique_ptr
의 한 인스턴스 에 의해서만 소유되며,
// Creates a dynamic int with value of 20 owned by a unique pointer
std::unique_ptr<int> ptr = std::make_unique<int>(20);
(참고 : std::unique_ptr
은 C ++ 11부터 사용할 수 있으며 std::make_unique
는 C ++ 14부터 사용할 수 있습니다.)
변수 ptr
만 동적으로 할당 된 int
대한 포인터를 보유합니다. 객체를 소유하고있는 유일한 포인터가 범위를 벗어나면 소유 된 객체가 삭제됩니다. 즉, 객체가 클래스 유형이고 해당 객체의 메모리가 해제되면 소멸자가 호출됩니다.
배열 유형과 함께 std::unique_ptr
및 std::make_unique
를 사용하려면 배열 전문화를 사용하십시오.
// Creates a unique_ptr to an int with value 59
std::unique_ptr<int> ptr = std::make_unique<int>(59);
// Creates a unique_ptr to an array of 15 ints
std::unique_ptr<int[]> ptr = std::make_unique<int[]>(15);
당신은 원시 포인터처럼 std::unique_ptr
접근 할 수있다.
원래의 스마트 포인터가 nullptr
가리키는 std::move
를 사용하여 스마트 포인터의 내용 소유권을 다른 포인터로 전송할 수 있습니다.
// 1. std::unique_ptr
std::unique_ptr<int> ptr = std::make_unique<int>();
// Change value to 1
*ptr = 1;
// 2. std::unique_ptr (by moving 'ptr' to 'ptr2', 'ptr' doesn't own the object anymore)
std::unique_ptr<int> ptr2 = std::move(ptr);
int a = *ptr2; // 'a' is 1
int b = *ptr; // undefined behavior! 'ptr' is 'nullptr'
// (because of the move command above)
unique_ptr
을 매개 변수로 함수에 전달 :
void foo(std::unique_ptr<int> ptr)
{
// Your code goes here
}
std::unique_ptr<int> ptr = std::make_unique<int>(59);
foo(std::move(ptr))
함수에서 unique_ptr
을 반환합니다. 이는 반환의 소유권 의미를 명확하게 전달하기 때문에 공장 함수를 작성하는 데있어 C ++에서 선호하는 11 가지 방법입니다. 호출자가 결과 unique_ptr
소유하고 이에 대한 책임이 있습니다.
std::unique_ptr<int> foo()
{
std::unique_ptr<int> ptr = std::make_unique<int>(59);
return ptr;
}
std::unique_ptr<int> ptr = foo();
이것을 다음과 비교하십시오 :
int* foo_cpp03();
int* p = foo_cpp03(); // do I own p? do I have to delete it at some point?
// it's not readily apparent what the answer is.
클래스 템플릿 make_unique
는 C ++ 14부터 제공됩니다. 수동으로 C ++ 11 코드에 손쉽게 추가 할 수 있습니다.
template<typename T, typename... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args&&... args)
{ return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
// Use make_unique for arrays
template<typename T>
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(size_t n)
{ return std::unique_ptr<T>(new typename std::remove_extent<T>::type[n]()); }
벙어리 스마트 포인터 ( std::auto_ptr
)와 달리, unique_ptr
은 벡터 할당 ( std::vector
아닌) 으로 인스턴스화 될 수도 있습니다. 이전 예제는 스칼라 할당에 대한 것이 었습니다. 예를 들어, 10 개의 요소에 대해 동적으로 할당 된 정수 배열을 가지려면 int[]
를 ( int
아닌) 템플릿 유형으로 지정합니다.
std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(10);
다음과 같이 단순화 할 수 있습니다.
auto arr_ptr = std::make_unique<int[]>(10);
자, arr_ptr
을 배열처럼 사용 arr_ptr
:
arr_ptr[2] = 10; // Modify third element
할당 취소에 대해 걱정할 필요가 없습니다. 이 템플릿 특수 버전은 생성자와 소멸자를 적절하게 호출합니다. 벡터화 된 버전의 unique_ptr
또는 vector
자체를 사용하는 것은 개인적인 선택입니다.
C ++ 11 이전 버전에서는 std::auto_ptr
을 사용할 수있었습니다. unique_ptr
과 달리 auto_ptr
을 복사 할 수 있습니다.이 때 소스 ptr
은 포함 된 포인터의 소유권을 잃고 대상은 그것을받습니다.
사용자 정의 삭제자를 사용하여 C 인터페이스에 대한 래퍼 생성
SDL2 와 같은 많은 C 인터페이스에는 자체 삭제 기능이 있습니다. 즉, 스마트 포인터를 직접 사용할 수 없습니다.
std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!
대신 자신 만의 Deleter를 정의해야합니다. 여기의 예제는 SDL_FreeSurface()
함수를 사용하여 해제해야하는 SDL_Surface
구조체를 사용하지만 다른 많은 C 인터페이스에도 적용 할 수 있어야합니다.
deleter는 포인터 인수로 호출 할 수 있어야하므로 예를 들어 간단한 함수 포인터 일 수 있습니다.
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
다른 호출 가능한 객체도 작동합니다 (예 : operator()
있는 클래스 operator()
.
struct SurfaceDeleter {
void operator()(SDL_Surface* surf) {
SDL_FreeSurface(surf);
}
};
std::unique_ptr<SDL_Surface, SurfaceDeleter> a(pointer, SurfaceDeleter{}); // safe
std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // equivalent to the above
// as the deleter is value-initialized
이것은 안전하고 zero overhead ( unique_ptr
을 사용하는 경우) 자동 메모리 관리를 제공 할뿐만 아니라 예외 안전성도 얻습니다.
Deleter는 unique_ptr
에 대한 유형의 일부이며, 구현시 빈 기본 최적화 를 사용하여 빈 사용자 정의 삭제 자의 크기가 변경되지 않도록 할 수 있습니다. 따라서 std::unique_ptr<SDL_Surface, SurfaceDeleter>
와 std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)>
는 같은 문제를 비슷한 방식으로 해결하지만 이전 유형은 여전히 포인터의 크기뿐입니다. 후자의 타입은 두 포인터 ( SDL_Surface*
와 함수 포인터 모두)를 가지고 있어야합니다! 자유 함수 사용자 정의 deleter를 사용하는 경우 빈 형식으로 함수를 래핑하는 것이 좋습니다.
참조 카운팅이 중요한 경우 unique_ptr
대신 shared_ptr
사용할 수 있습니다. shared_ptr
항상 deleter를 저장하며 API에서 유용 할 수있는 deleter의 유형을 지 웁니다. unique_ptr
비해 shared_ptr
을 사용하는 단점은 참조 자 수를 유지하기위한 deleter 및 성능 비용을 저장하는 데 더 높은 메모리 비용이 포함된다는 것입니다.
// deleter required at construction time and is part of the type
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
// deleter is only required at construction time, not part of the type
std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface);
template auto
으로 우리는 커스텀 deleter를 더 쉽게 래핑 할 수 있습니다 :
template <auto DeleteFn>
struct FunctionDeleter {
template <class T>
void operator()(T* ptr) {
DeleteFn(ptr);
}
};
template <class T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;
위의 예는 다음과 같습니다.
unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);
여기서 auto
의 목적은 void
(예 : SDL_FreeSurface
)이든 SDL_FreeSurface
(예 : fclose
) 모든 자유 함수를 처리하는 것입니다.
이동 의미없는 고유 한 소유권 (auto_ptr)
참고 : std::auto_ptr
은 C ++ 11에서 사용되지 않으며 C ++ 17에서 제거됩니다. C ++ 03 이전 버전을 사용하도록 강요 받고 조심스럽게 사용하려는 경우에만 사용해야합니다. std::auto_ptr
동작을 대체하려면 std::move
와 함께 unique_ptr로 이동하는 것이 좋습니다.
우리가 std::unique_ptr
을 갖기 전에, 우리는 이동의 의미를 std::unique_ptr
전에 std::auto_ptr
가졌다. std::auto_ptr
은 고유 한 소유권을 제공하지만 복사시 소유권을 이전합니다.
모든 스마트 포인터와 마찬가지로 std::auto_ptr
자동으로 자원을 정리합니다 ( RAII 참조).
{
std::auto_ptr<int> p(new int(42));
std::cout << *p;
} // p is deleted here, no memory leaked
한 명의 소유자 만 허용합니다.
std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px;
// px is now empty
이렇게하면 std :: auto_ptr을 사용하여 의도하지 않은 소유권을 잃을 위험이있는 소유권을 명확하고 고유하게 유지할 수 있습니다.
void f(std::auto_ptr<X> ) {
// assumes ownership of X
// deletes it at end of scope
};
std::auto_ptr<X> px = ...;
f(px); // f acquires ownership of underlying X
// px is now empty
px->foo(); // NPE!
// px.~auto_ptr() does NOT delete
소유권 이전은 "복사"생성자에서 발생했습니다. auto_ptr
의 복사 생성자와 복사 할당 연산자는 피연산자를 수정 가능하도록 비 const
참조로 사용합니다. 구현 예는 다음과 같습니다.
template <typename T>
class auto_ptr {
T* ptr;
public:
auto_ptr(auto_ptr& rhs)
: ptr(rhs.release())
{ }
auto_ptr& operator=(auto_ptr& rhs) {
reset(rhs.release());
return *this;
}
T* release() {
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
void reset(T* tmp = nullptr) {
if (ptr != tmp) {
delete ptr;
ptr = tmp;
}
}
/* other functions ... */
};
이것은 복사 의미론을 깨뜨리는데, 이는 객체 복사가 두 개의 동등한 버전으로 이루어져야한다는 것을 의미합니다. 복사 가능한 유형 인 T
과 같이 작성할 수 있습니다.
T a = ...;
T b(a);
assert(b == a);
그러나 auto_ptr
의 경우에는 그렇지 않습니다. 따라서 auto_ptr
을 컨테이너에 두는 것은 안전하지 않습니다.
이것을 가리키는 shared_ptr 얻기
enable_shared_from_this
는 this
위해 유효한 shared_ptr
인스턴스를 얻을 수있게합니다.
클래스 템플릿에서 클래스를 파생하여 enable_shared_from_this
, 당신은 방법 상속 shared_from_this
반환 shared_ptr
에 인스턴스를 this
.
객체가로 작성되어야합니다 shared_ptr
첫 번째 장소에서 :
#include <memory>
class A: public enable_shared_from_this<A> {
};
A* ap1 =new A();
shared_ptr<A> ap2(ap1); // First prepare a shared pointer to the object and hold it!
// Then get a shared pointer to the object from the object itself
shared_ptr<A> ap3 = ap1->shared_from_this();
int c3 =ap3.use_count(); // =2: pointing to the same object
주 (2) 생성자 내에서 enable_shared_from_this
호출 할 수 없습니다.
#include <memory> // enable_shared_from_this
class Widget : public std::enable_shared_from_this< Widget >
{
public:
void DoSomething()
{
std::shared_ptr< Widget > self = shared_from_this();
someEvent -> Register( self );
}
private:
...
};
int main()
{
...
auto w = std::make_shared< Widget >();
w -> DoSomething();
...
}
shared_ptr
소유하지 않은 객체 shared_from_this()
예 : 로컬 자동 객체 또는 전역 객체 shared_from_this()
에서 shared_from_this()
를 사용하면 동작이 정의되지 않습니다. C ++ 17 이후로 std::bad_alloc
던졌습니다.
생성자에서 shared_from_this()
를 사용하는 것은 shared_ptr
소유하지 않은 객체 shared_ptr
객체를 사용하는 것과 같습니다. 왜냐하면 생성자가 반환 한 후에 shared_ptr
이 객체를 소유하기 때문입니다.
주조 표준 :: shared_ptr 포인터
std::shared_ptr
에서 static_cast
, const_cast
, dynamic_cast
및 reinterpret_cast
를 직접 사용하여 인수로 전달되는 포인터로 소유권을 공유하는 포인터를 검색 할 수 없습니다. 대신 함수 std::static_pointer_cast
, std::const_pointer_cast
, std::dynamic_pointer_cast
및 std::reinterpret_pointer_cast
사용해야합니다.
struct Base { virtual ~Base() noexcept {}; };
struct Derived: Base {};
auto derivedPtr(std::make_shared<Derived>());
auto basePtr(std::static_pointer_cast<Base>(derivedPtr));
auto constBasePtr(std::const_pointer_cast<Base const>(basePtr));
auto constDerivedPtr(std::dynamic_pointer_cast<Derived const>(constBasePtr));
std::reinterpret_pointer_cast
는 N3920에서 제안하고 2014 년 2 월 에 Library Fundamentals TS 에 채택 되었기 때문에 C ++ 11 및 C ++ 14에서는 사용할 수 없습니다. 그러나 다음과 같이 구현할 수 있습니다.
template <typename To, typename From>
inline std::shared_ptr<To> reinterpret_pointer_cast(
std::shared_ptr<From> const & ptr) noexcept
{ return std::shared_ptr<To>(ptr, reinterpret_cast<To *>(ptr.get())); }
스마트 포인터 작성 : value_ptr
value_ptr
은 값처럼 동작하는 스마트 포인터입니다. 복사되면 내용을 복사합니다. 생성되면 내용을 생성합니다.
// Like std::default_delete:
template<class T>
struct default_copier {
// a copier must handle a null T const* in and return null:
T* operator()(T const* tin)const {
if (!tin) return nullptr;
return new T(*tin);
}
void operator()(void* dest, T const* tin)const {
if (!tin) return;
return new(dest) T(*tin);
}
};
// tag class to handle empty case:
struct empty_ptr_t {};
constexpr empty_ptr_t empty_ptr{};
// the value pointer type itself:
template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>,
class Base=std::unique_ptr<T, Deleter>
>
struct value_ptr:Base, private Copier {
using copier_type=Copier;
// also typedefs from unique_ptr
using Base::Base;
value_ptr( T const& t ):
Base( std::make_unique<T>(t) ),
Copier()
{}
value_ptr( T && t ):
Base( std::make_unique<T>(std::move(t)) ),
Copier()
{}
// almost-never-empty:
value_ptr():
Base( std::make_unique<T>() ),
Copier()
{}
value_ptr( empty_ptr_t ) {}
value_ptr( Base b, Copier c={} ):
Base(std::move(b)),
Copier(std::move(c))
{}
Copier const& get_copier() const {
return *this;
}
value_ptr clone() const {
return {
Base(
get_copier()(this->get()),
this->get_deleter()
),
get_copier()
};
}
value_ptr(value_ptr&&)=default;
value_ptr& operator=(value_ptr&&)=default;
value_ptr(value_ptr const& o):value_ptr(o.clone()) {}
value_ptr& operator=(value_ptr const&o) {
if (o && *this) {
// if we are both non-null, assign contents:
**this = *o;
} else {
// otherwise, assign a clone (which could itself be null):
*this = o.clone();
}
return *this;
}
value_ptr& operator=( T const& t ) {
if (*this) {
**this = t;
} else {
*this = value_ptr(t);
}
return *this;
}
value_ptr& operator=( T && t ) {
if (*this) {
**this = std::move(t);
} else {
*this = value_ptr(std::move(t));
}
return *this;
}
T& get() { return **this; }
T const& get() const { return **this; }
T* get_pointer() {
if (!*this) return nullptr;
return std::addressof(get());
}
T const* get_pointer() const {
if (!*this) return nullptr;
return std::addressof(get());
}
// operator-> from unique_ptr
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&... args ) {
return {std::make_unique<T>(std::forward<Args>(args)...)};
}
이 특정 value_ptr은 empty_ptr_t
하거나 empty_ptr_t
이동할 경우에만 비어 있습니다. 그것은 그것이 unique_ptr
이라는 사실을 드러내 기 때문에 explicit operator bool() const
가 그것에 작용합니다. .get()
이 참조를 반환하도록 변경되었으므로 (거의 빈 상태가 .get_pointer()
가 대신 포인터를 반환합니다.
이 스마트 포인터는 값 의미를 원하지만 pImpl
의 내용을 구현 파일 외부에 노출하고 싶지 않은 pImpl
경우에 유용 할 수 있습니다.
기본이 아닌 Copier
사용하면 파생 된 인스턴스를 생성하고 값 유형으로 변환하는 방법을 알고있는 가상 기본 클래스도 처리 할 수 있습니다.