어떤 C ++ 스마트 포인터 구현을 사용할 수 있습니까?


121

비교, 장점, 단점 및 사용시기

이것은 가비지 컬렉션 스레드 에서 파생 된 것으로, 간단한 답변이라고 생각한 것이 특정 스마트 포인터 구현에 대한 많은 의견을 생성하여 새 게시물을 시작할 가치가있는 것처럼 보였습니다.

궁극적으로 질문은 C ++에서 스마트 포인터의 다양한 구현이 무엇이며 어떻게 비교합니까? 단순한 장단점 또는 예외와 그렇지 않으면 작동해야한다고 생각할 수있는 문제가 있습니다.

나는 내가 사용했거나 적어도 훑어 본 몇 가지 구현을 게시했으며 아래 답변으로 사용을 고려했으며 100 % 정확하지 않을 수있는 차이점과 유사점에 대한 나의 이해도 있으므로 필요에 따라 사실을 확인하거나 수정하십시오.

목표는 몇 가지 새로운 객체와 라이브러리에 대해 배우거나 이미 널리 사용되고있는 기존 구현에 대한 내 사용법과 이해를 수정하여 다른 사람들에게 적절한 참조로 만드는 것입니다.


5
이 질문에 대한 답변으로 다시 게시되어야한다고 생각하며 질문은 실제 질문으로 만들어졌습니다. 그렇지 않으면 사람들이 이것을 "진짜 질문이 아님"으로 종결시킬 것입니다.
strager

3
ATL 스마트 포인터 또는 OpenSceneGraphosg::ref_ptr 와 같은 모든 종류의 다른 스마트 포인터 있습니다.
James McNellis

11
여기에 질문이 있습니까?
Cody Gray

6
나는 당신이 오해했다고 생각합니다 std::auto_ptr. std::auto_ptr_ref의 디자인 세부 사항입니다 std::auto_ptr. std::auto_ptr가비지 수집과는 아무런 관련이 없습니다. 주된 목적은 특히 함수 호출 및 함수 반환 상황에서 예외적으로 안전한 소유권 이전을 허용하는 것입니다. std::unique_ptrC ++가 이동과 복사를 구분할 수 있도록 변경되었고이를 활용하기 위해 표준 컨테이너가 변경 되었기 때문에 표준 컨테이너에서 인용하는 "문제"만 해결할 수 있습니다.
CB Bailey

3
당신은 똑똑한 포인터에 대한 전문가는 아니지만 요약이 매우 철저하고 정확하다고 auto_ptr_ref말합니다 (구현 세부 사항 에 대한 사소한 문제를 제외하고 ). 그래도이 질문을 답변 으로 게시 하고 질문을 실제 질문으로 재구성 해야한다는 데 동의합니다 . 그러면 나중에 참조 할 수 있습니다.
Konrad Rudolph

답변:


231

C ++ 03

std::auto_ptr-아마도 제한된 쓰레기 수거 시설만을 제공하는 초안 증후군으로 고통받는 원본 중 하나 일 것입니다. 첫 번째 단점은 delete파괴시 호출 하여 배열 할당 객체 ( new[]) 를 보유하는 데 허용되지 않는다는 것 입니다. 포인터의 소유권을 가지므로 두 개의 자동 포인터가 동일한 객체를 포함해서는 안됩니다. 할당은 소유권을 이전하고 rvalue 자동 포인터를 널 포인터로 재설정합니다 . 이는 아마도 최악의 단점으로 이어집니다. 앞서 언급 한 복사가 불가능하기 때문에 STL 컨테이너 내에서 사용할 수 없습니다. 모든 사용 사례에 대한 최종 타격은 C ++의 다음 표준에서 더 이상 사용되지 않을 예정이라는 것입니다.

std::auto_ptr_ref-이것은 스마트 포인터가 아니며 실제로 std::auto_ptr특정 상황에서 복사 및 할당을 허용하기 위해 함께 사용되는 디자인 세부 사항 입니다. 특히 소유권을 이전 하는 이동 생성자 라고도 알려진 Colvin-Gibbons 트릭을 사용하여 상수 가 아닌 std::auto_ptr것을 lvalue 로 변환하는 데 사용할 수 있습니다 .

반대로 std::auto_ptr자동 가비지 수집을위한 범용 스마트 포인터로 사용되도록 의도되지 않았을 수 있습니다. 나의 제한된 이해와 가정의 대부분은 Herb Sutter의 auto_ptr의 효과적인 사용을 기반으로 하며 항상 가장 최적화 된 방식은 아니지만 정기적으로 사용합니다.


C ++ 11

std::unique_ptr-이것은 배열 작업, 개인 복사 생성자를 통한 lvalue 보호, STL 컨테이너 및 알고리즘 등과 함께 사용할 수있는 것과 같은 std::auto_ptr약점을 수정하기위한 주요 개선 사항을 제외하고는이를 대체 할 우리의 친구입니다 . 성능 오버 헤드이기 때문에 메모리 풋 프린트가 제한되어있어 원시 포인터를 대체하거나 소유하는 것으로 더 적절하게 설명 할 수있는 이상적인 후보입니다. "고유 한"이 의미하는 것처럼 포인터의 소유자는 이전 .std::auto_ptrstd::auto_ptr

std::shared_ptr-TR1을 기반으로 boost::shared_ptr하지만 앨리어싱 및 포인터 산술도 포함하도록 개선되었습니다. 간단히 말해 동적으로 할당 된 객체 주위에 참조 카운트 스마트 포인터를 감 쌉니다. "공유"가 의미하는 것처럼 마지막 공유 포인터의 마지막 참조가 범위를 벗어나면 개체가 적절하게 삭제 될 때 포인터가 둘 이상의 공유 포인터에 의해 소유 될 수 있습니다. 또한 스레드로부터 안전하며 대부분의 경우 불완전한 유형을 처리 할 수 ​​있습니다. 기본 할당자를 사용하여 하나의 힙 할당으로 std::make_shared를 효율적으로 구성하는 데 사용할 수 있습니다 std::shared_ptr.

std::weak_ptr-마찬가지로 TR1 및 boost::weak_ptr. 이것은 a가 소유 한 객체에 대한 참조 std::shared_ptr이므로 std::shared_ptr참조 횟수가 0으로 떨어지더라도 객체 삭제를 방지하지 않습니다 . 원시 포인터에 액세스하려면 먼저 소유 한 포인터가 만료되어 이미 소멸 된 경우 빈 값을 반환하는 std::shared_ptrby 호출 에 액세스해야합니다 . 이것은 여러 스마트 포인터를 사용할 때 무한정 참조 카운트를 방지하는 데 주로 유용합니다.lockstd::shared_ptr


후원

boost::shared_ptr-아마도 가장 다양한 시나리오 (STL, PIMPL, RAII 등)에서 사용하기 가장 쉬운 방법 일 것입니다. 이것은 공유 참조 카운트 스마트 포인터입니다. 어떤 상황에서 성능과 오버 헤드에 대한 몇 가지 불만을 들었지만 그 주장이 무엇인지 기억할 수 없기 때문에 무시했을 것입니다. 분명히 그것은 보류중인 표준 C ++ 객체가 될만큼 충분히 대중적이었고 스마트 포인터와 관련된 표준에 대한 결점은 떠오르지 않았습니다.

boost::weak_ptr-에 대한 이전 설명과 매우 유사하게이 std::weak_ptr구현을 기반으로하여 boost::shared_ptr. lock()"강력한"공유 포인터에 액세스하기 위해 당연히 호출 하는 것은 아니며 이미 파괴되었을 수 있으므로 유효한지 확인해야합니다. 반환 된 공유 포인터를 저장하지 않도록하고 작업이 완료 되 자마자 범위를 벗어나게하십시오. 그렇지 않으면 참조 카운트가 중단되고 객체가 파괴되지 않는 순환 참조 문제로 바로 돌아갑니다.

boost::scoped_ptr-이것은 boost::shared_ptr사용 가능한 경우 보다 더 나은 성능을 발휘하도록 설계된 오버 헤드가 거의없는 간단한 스마트 포인터 클래스입니다 . std::auto_ptr특히 STL 컨테이너의 요소 또는 동일한 개체에 대한 여러 포인터로 안전하게 사용할 수 없다는 사실 과 비슷 합니다.

boost::intrusive_ptr-나는 이것을 사용한 적이 없지만 내 이해에 따르면 자신의 스마트 포인터 호환 클래스를 만들 때 사용하도록 설계되었습니다. 참조 카운팅을 직접 구현해야합니다. 클래스를 일반화하려면 몇 가지 메서드를 구현해야합니다. 또한 자체 스레드 안전성을 구현해야합니다. 플러스 측면에서 이것은 아마도 당신이 원하는 "스마트 성"의 양 또는 정도를 정확히 선택하고 선택하는 가장 맞춤형 방법을 제공 할 것입니다. intrusive_ptr일반적으로 shared_ptr개체 당 단일 힙 할당이 가능하기 때문에 보다 효율적 입니다. (Arvid에게 감사드립니다)

boost::shared_array-이것은 boost::shared_ptr어레 이용입니다. 기본적으로 new [], operator[]및 물론 delete []구워집니다. 이것은 STL 컨테이너에서 사용할 수 있으며 내가 아는 한 모두 boost:shared_ptr사용할 수는 있지만 사용할 수 없습니다 boost::weak_ptr. 그러나 boost::shared_ptr<std::vector<>>유사한 기능을 위해를 대신 사용 boost::weak_ptr하고 참조 에 사용할 수있는 기능을 다시 얻을 수 있습니다 .

boost::scoped_array-이것은 boost::scoped_ptr어레 이용입니다. 와 같이 boost::shared_array모든 필요한 배열 선량에서 소성된다. 이것은 비 복사 가능한이고 그래서 STL 용기에 사용될 수 없다. 나는 당신이 이것을 사용하고 싶어하는 거의 모든 곳에서 아마도 std::vector. 실제로 더 빠르거나 오버 헤드가 적은 것을 결정한 적이 없지만이 범위 배열은 STL 벡터보다 훨씬 덜 관여하는 것 같습니다. 스택에 할당을 유지하려면 boost::array대신 고려하십시오 .


Qt

QPointer-Qt 4.0에 도입 된 이것은 "약한"스마트 포인터로 QObject, 클래스와 파생 클래스 에서만 작동하며 , Qt 프레임 워크에서는 거의 모든 것이기 때문에 실제로 제한이 없습니다. 그러나 "강력한"포인터를 제공하지 않는다는 제한이 있으며 기본 개체가 유효한지 isNull()확인할 수 있지만 특히 다중 스레드 환경에서 검사를 통과 한 직후에 개체가 파괴되는 것을 찾을 수 있습니다. Qt 사람들은 이것이 비추천이라고 생각합니다.

QSharedDataPointer-이것은 boost::intrusive_ptr스레드 안전성이 내장되어 있지만 잠재적으로 비교할 수있는 "강력한"스마트 포인터 이지만 서브 클래 싱으로 수행 할 수있는 참조 카운팅 메서드 ( refderef) 를 포함해야합니다 QSharedData. 많은 Qt와 마찬가지로 객체는 충분한 상속을 통해 가장 잘 사용되며 모든 것이 의도 된 디자인 인 것처럼 보입니다.

QExplicitlySharedDataPointer- QSharedDataPointer암시 적으로를 호출하지 않는다는 점 을 제외하면 과 매우 유사합니다 detach(). QSharedDataPointer참조 횟수가 0으로 떨어진 후 정확히 언제 분리해야하는지에 대한 제어의 약간의 증가가 완전히 새로운 개체의 가치가 없기 때문에이 버전 2.0을 호출 합니다.

QSharedPointer-원자 참조 계산, 스레드 안전, 공유 가능한 포인터, 사용자 지정 삭제 (배열 지원), 스마트 포인터가 있어야하는 모든 것 같은 소리. 이것은 내가 주로 Qt에서 스마트 포인터로 사용하는 boost:shared_ptr것이며 Qt의 많은 객체와 같이 훨씬 더 많은 오버 헤드 가 있지만 비교할 수 있습니다 .

QWeakPointer-반복되는 패턴을 느끼십니까? 마찬가지로 std::weak_ptrboost::weak_ptr이와 함께 사용 QSharedPointer하면 그렇지 않으면 오브젝트가 삭제되지 않을 발생할 것이 스마트 포인터 사이의 참조를 필요로 할 때.

QScopedPointer-이 이름은 또한 친숙하게 보이며 실제로 boost::scoped_ptr공유 및 약한 포인터의 Qt 버전과는 달리 사실을 기반으로 합니다. 그것은 오버 헤드없이 하나의 소유자 스마트 포인터를 제공하는 기능 QSharedPointer호환성, 예외 안전 코드에 대한 더 적합하게하는, 당신이 사용할 수있는 모든 것을 std::auto_ptrboost::scoped_ptr에 대한.


1
두 가지 언급 할 가치가 있다고 생각합니다. intrusive_ptr일반적으로 shared_ptr는 객체 당 단일 힙 할당을 가질 수 있기 때문에 보다 효율적 입니다. shared_ptr일반적으로 참조 카운터에 대해 별도의 작은 힙 개체를 할당합니다. std::make_shared두 세계를 최대한 활용하는 데 사용할 수 있습니다. shared_ptr단일 힙 할당으로.
Arvid 2011 년

아마도 관련없는 질문이 있습니다. 모든 포인터를 shared_ptrs로 교체하여 가비지 수집을 구현할 수 있습니까 ? (순환 참조 해결은 계산하지 않음)
Seth Carnegie

@Seth Carnegie : 모든 포인터가 무료 스토어에 할당 된 것을 가리키는 것은 아닙니다.
In silico 2011 년

2
@the_mandrill하지만 소유하는 클래스의 소멸자가 클라이언트 코드와는 별개의 번역 단위 (.cpp-file)에 정의되어 있으면 작동합니다. 이 변환 단위는 일반적으로 Pimpl의 완전한 정의를 알고 있으므로 해당 소멸자 (auto_ptr을 파괴 할 때)가 Pimpl을 올바르게 파괴합니다. 나는 또한 경고를 보았을 때 이것에 대한 두려움이 있었지만 시도해 보았고 작동했습니다 (Pimpl의 소멸자가 호출됩니다). 추신 : 답글을 보려면 @-구문을 사용하십시오.
Christian Rau

2
문서에 적절한 링크를 추가하여 목록의 유용성을 높였습니다.
ulidtko 2013 년


1

주어진 것 외에도 안전 지향적 인 것들이 있습니다.

SaferCPlusPlus

mse::TRefCountingPointer같은 참조 계산 스마트 포인터 std::shared_ptr입니다. 차이점 mse::TRefCountingPointer은 더 안전하고 더 작고 빠르지 만 스레드 안전 메커니즘이 없다는 것입니다. 그리고 항상 유효하게 할당 된 객체를 가리키는 것으로 안전하게 가정 할 수있는 "not null"및 "fixed"(재 타겟팅 불가능) 버전으로 제공됩니다. 따라서 기본적으로 대상 객체가 비동기 스레드간에 공유되는 경우를 사용 std::shared_ptr하고 그렇지 않은 경우 mse::TRefCountingPointer더 최적입니다.

mse::TScopeOwnerPointer과 유사 boost::scoped_ptr하지만,와 함께 작품 mse::TScopeFixedPointer등의 "강한 약한"포인터 관계 종류 std::shared_ptrstd::weak_ptr.

mse::TScopeFixedPointer스택에 할당되거나 "소유"포인터가 스택에 할당 된 개체를 가리 킵니다. 런타임 비용없이 컴파일 시간 안전성을 향상시키는 기능이 (의도적으로) 제한되어 있습니다. "범위"포인터의 요점은 본질적으로 (런타임) 안전 메커니즘이 필요하지 않을 정도로 간단하고 결정적인 일련의 상황을 식별하는 것입니다.

mse::TRegisteredPointer대상 개체가 소멸 될 때 값이 자동으로 null_ptr로 설정된다는 점을 제외하면 원시 포인터처럼 동작합니다. 대부분의 상황에서 원시 포인터의 일반적인 대체물로 사용할 수 있습니다. 원시 포인터와 마찬가지로 본질적인 스레드 안전성이 없습니다. 그러나 그 대가로 스택에 할당 된 객체를 대상으로하는 데 문제가 없습니다 (및 해당 성능 이점을 얻음). 런타임 검사가 활성화되면이 포인터는 유효하지 않은 메모리에 액세스하는 것으로부터 안전합니다. mse::TRegisteredPointer유효한 객체를 가리킬 때 원시 포인터와 동일한 동작을 갖기 때문에 컴파일 타임 지시문을 사용하여 "비활성화"(해당 원시 포인터로 자동 교체) 할 수 있으므로 디버그 / 테스트에서 버그를 잡는 데 사용할 수 있습니다. 릴리스 모드에서 오버 헤드 비용이 발생하지 않는 동안 / 베타 모드.

다음 은 그 이유와 사용 방법을 설명하는 기사입니다. (참고, 뻔뻔한 플러그.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.