비교, 장점, 단점 및 사용시기
이것은 가비지 컬렉션 스레드 에서 파생 된 것으로, 간단한 답변이라고 생각한 것이 특정 스마트 포인터 구현에 대한 많은 의견을 생성하여 새 게시물을 시작할 가치가있는 것처럼 보였습니다.
궁극적으로 질문은 C ++에서 스마트 포인터의 다양한 구현이 무엇이며 어떻게 비교합니까? 단순한 장단점 또는 예외와 그렇지 않으면 작동해야한다고 생각할 수있는 문제가 있습니다.
나는 내가 사용했거나 적어도 훑어 본 몇 가지 구현을 게시했으며 아래 답변으로 사용을 고려했으며 100 % 정확하지 않을 수있는 차이점과 유사점에 대한 나의 이해도 있으므로 필요에 따라 사실을 확인하거나 수정하십시오.
목표는 몇 가지 새로운 객체와 라이브러리에 대해 배우거나 이미 널리 사용되고있는 기존 구현에 대한 내 사용법과 이해를 수정하여 다른 사람들에게 적절한 참조로 만드는 것입니다.
답변
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_ptr
std::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_ptr
by 호출 에 액세스해야합니다 . 이것은 여러 스마트 포인터를 사용할 때 무한정 참조 카운트를 방지하는 데 주로 유용합니다.lock
std::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
스레드 안전성이 내장되어 있지만 잠재적으로 비교할 수있는 “강력한”스마트 포인터 이지만 서브 클래 싱으로 수행 할 수있는 참조 카운팅 메서드 ( ref
및 deref
) 를 포함해야합니다 QSharedData
. 많은 Qt와 마찬가지로 객체는 충분한 상속을 통해 가장 잘 사용되며 모든 것이 의도 된 디자인 인 것처럼 보입니다.
QExplicitlySharedDataPointer
– QSharedDataPointer
암시 적으로를 호출하지 않는다는 점 을 제외하면 과 매우 유사합니다 detach()
. QSharedDataPointer
참조 횟수가 0으로 떨어진 후 정확히 언제 분리해야하는지에 대한 제어의 약간의 증가가 완전히 새로운 개체의 가치가 없기 때문에이 버전 2.0을 호출 합니다.
QSharedPointer
-원자 참조 계산, 스레드 안전, 공유 가능한 포인터, 사용자 지정 삭제 (배열 지원), 스마트 포인터가 있어야하는 모든 것 같은 소리. 이것은 내가 주로 Qt에서 스마트 포인터로 사용하는 boost:shared_ptr
것이며 Qt의 많은 객체와 같이 훨씬 더 많은 오버 헤드 가 있지만 비교할 수 있습니다 .
QWeakPointer
-반복되는 패턴을 느끼십니까? 마찬가지로 std::weak_ptr
및 boost::weak_ptr
이와 함께 사용 QSharedPointer
하면 그렇지 않으면 오브젝트가 삭제되지 않을 발생할 것이 스마트 포인터 사이의 참조를 필요로 할 때.
QScopedPointer
-이 이름은 또한 친숙하게 보이며 실제로 boost::scoped_ptr
공유 및 약한 포인터의 Qt 버전과는 달리 사실을 기반으로 합니다. 그것은 오버 헤드없이 하나의 소유자 스마트 포인터를 제공하는 기능 QSharedPointer
호환성, 예외 안전 코드에 대한 더 적합하게하는, 당신이 사용할 수있는 모든 것을 std::auto_ptr
나 boost::scoped_ptr
에 대한.
답변
정책 기반 스마트 포인터를 구현하는 Loki 도 있습니다 .
정책 기반 스마트 포인터에 대한 다른 참조는 많은 컴파일러에 의한 다중 상속과 함께 빈 기본 최적화의 지원이 부족한 문제를 해결합니다.
답변
주어진 것 외에도 안전 지향적 인 것들이 있습니다.
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_ptr
와 std::weak_ptr
.
mse::TScopeFixedPointer
스택에 할당되거나 “소유”포인터가 스택에 할당 된 개체를 가리 킵니다. 런타임 비용없이 컴파일 시간 안전성을 향상시키는 기능이 (의도적으로) 제한되어 있습니다. “범위”포인터의 요점은 본질적으로 (런타임) 안전 메커니즘이 필요하지 않을 정도로 간단하고 결정적인 일련의 상황을 식별하는 것입니다.
mse::TRegisteredPointer
대상 개체가 소멸 될 때 값이 자동으로 null_ptr로 설정된다는 점을 제외하면 원시 포인터처럼 동작합니다. 대부분의 상황에서 원시 포인터의 일반적인 대체물로 사용할 수 있습니다. 원시 포인터와 마찬가지로 본질적인 스레드 안전성이 없습니다. 그러나 그 대가로 스택에 할당 된 객체를 대상으로하는 데 문제가 없습니다 (및 해당 성능 이점을 얻음). 런타임 검사가 활성화되면이 포인터는 유효하지 않은 메모리에 액세스하는 것으로부터 안전합니다. mse::TRegisteredPointer
유효한 객체를 가리킬 때 원시 포인터와 동일한 동작을 갖기 때문에 컴파일 타임 지시문을 사용하여 “비활성화”(해당 원시 포인터로 자동 교체) 할 수 있으므로 디버그 / 테스트에서 버그를 잡는 데 사용할 수 있습니다. 릴리스 모드에서 오버 헤드 비용이 발생하지 않는 동안 / 베타 모드.
다음 은 그 이유와 사용 방법을 설명하는 기사입니다. (참고, 뻔뻔한 플러그.)