언제 어떤 종류의 포인터를 사용합니까?


228

좋아, 내가 생계를 위해 C ++을 마지막으로 썼을 std::auto_ptr때 std lib가 사용 가능한 boost::shared_ptr모든 것이었고 모든 분노였습니다. 나는 다른 스마트 포인터 유형 부스트를 실제로 보지 않았다. C ++ 11은 현재 일부 유형의 부스트가 제공되었지만 모든 유형을 제공하지는 않습니다.

그렇다면 어떤 스마트 포인터를 언제 사용할지 결정하는 간단한 알고리즘이 있습니까? 벙어리 포인터 (와 같은 원 포인터 T*) 및 나머지 부스트 스마트 포인터 에 관한 조언을 포함하는 것이 바람직 합니다. (이와 같은 일 좋을 것입니다).



1
나는 누군가 가이 STL 선택 흐름도 와 같은 멋진 편리한 흐름도를 생각해 내기를 바라고있다 .
Alok Save

1
@Als : 아, 정말 좋은 것입니다! 나는 그것을 FAQ했다.
sbi

6
@Deduplicator 복제본에 가깝지 않습니다. 연결된 질문에 " 스마트 포인터를 사용해야 하는 시기" 가 표시 되고이 질문은 " 스마트 포인터를 언제 사용 합니까?"입니다. 즉, 이것은 표준 스마트 포인터의 다른 용도를 분류하고 있습니다. 연결된 질문은 이것을하지 않습니다. 차이는 작지만 큰 것입니다.
Rapptz

답변:


183

공유 소유권 : 및 채택 된 표준은 거의 자신과 동일 부스트 대응 . 자원을 공유해야하고 마지막으로 생존 할 자원을 모를 때 사용하십시오. 주기에 영향을 미치지 않고 수명에 영향을 미치지 않고 공유 리소스를 관찰하는 데 사용 합니다. 사이클 은 일반적으로 발생하지 않아야합니다. 두 리소스는 서로를 소유 할 수 없습니다.
shared_ptrweak_ptrweak_ptrshared_ptr

Boost는 추가로 오퍼를 제공하는데 shared_array, 이는에 대한 적절한 대안 일 수 shared_ptr<std::vector<T> const>있습니다.

다음으로, intrusive_ptr자원이 참조 계산 관리를 이미 제공하고 RAII 원칙에 채택하려는 경우 가벼운 솔루션 인 Boost 제공합니다 . 이것은 표준에 의해 채택되지 않았습니다.

고유 소유권 :
Boost에는 또한 scoped_ptr복사 할 수 없으며 삭제자를 지정할 수없는을 가지고 있습니다. std::unique_ptr이다 boost::scoped_ptr스테로이드에와 있어야합니다 스마트 포인터를 필요로 할 때 기본 선택 . 그것은 당신이 그것의 템플릿 인수로 삭제기를 지정할 있으며 달리 움직일 수boost::scoped_ptr 있습니다. 복사 가능한 유형이 필요한 작업을 사용하지 않는 한 STL 컨테이너에서도 완벽하게 사용할 수 있습니다.

Boost는 배열 버전 :을 가지고 있음을 주목하십시오.이 scoped_array표준 은 ( r로) 포인터 대신 포인터 std::unique_ptr<T[]>를 사용할 부분 전문화를 요구함으로써 통합되었습니다 . 및 대신 제공합니다 .delete[]deletedefault_deletestd::unique_ptr<T[]>operator[]operator*operator->

std::auto_ptr표준에 아직도있다, 그러나이되어 사용되지 . §D.10 [depr.auto.ptr]

클래스 템플릿 auto_ptr은 더 이상 사용되지 않습니다. [ 참고 : 클래스 템플릿 unique_ptr(20.7.1)은 더 나은 솔루션을 제공합니다. — 끝 노트 ]

소유권 없음 : 리소스에
대한 비 소유 참조 에 대해 벙어리 포인터 (원시 포인터) 또는 참조를 사용 하고 리소스가 참조 객체 / 범위 보다 오래 지속 된다는 것을 알고있는 경우 . nullable 또는 resettability가 필요할 때 참조를 선호하고 raw 포인터를 사용하십시오.

당신은 자원에 비 소유 참조를 원하는,하지만 당신은 리소스 개체를 오래 살 것입니다 경우 참조는, A의 자원을 팩 것을 모르는 경우 shared_ptr와 사용하십시오 weak_ptr- 부모가 있는지 테스트 할 수 shared_ptr와 살아 lock있는 것, shared_ptr자원이 여전히 존재하는 경우 널이 아닌를 리턴하십시오 . 리소스가 죽었는지 테스트하려면을 사용하십시오 expired. 둘은 비슷하게 들릴 수 있지만 동시 실행의 경우에는 expired단일 문에 대한 반환 값만 보장하므로 매우 다릅니다 . 겉보기에 순진한 시험

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

잠재적 인 경쟁 조건입니다.


1
소유권이없는 경우 참조가 자르지 않는 소유권 재설정 가능성 이 필요하지 않은 경우 포인터에 대한 참조를 선호해야 합니다. 심지어 원래 객체를 다시 작성하고 shared_ptr소유하지 않는 포인터를 weak_ptr...
데이비드 로드리게스 - dribeas

2
나는 포인터에 대한 참조를 의미하는 것이 아니라 포인터 대신에 참조를 의미했다 . 소유권이없는 경우 재설정 가능성 (또는 Null 허용, 재설정 할 수없는 Null 허용이 상당히 제한되지 않은 경우)이 아니라면 우선 포인터 대신 일반 참조를 사용할 수 있습니다.
David Rodríguez-dribeas

1
@David : 아, 알겠습니다. :) 그래, 참고 문헌은 그다지 나쁘지 않다. 나는 그런 경우에도 개인적으로 선호한다. 추가하겠습니다.
Xeo

1
@Xeo : 하지 말아야 할 shared_array<T>대안 : 성장할 수 없습니다. shared_ptr<T[]>shared_ptr<vector<T>>
R. Martinho Fernandes 2012 년

1
@GregroyCurrie : 저것은 ... 정확히 내가 쓴 것? 나는 그것이 잠재적 인 경쟁 조건의 예라고 말했다.
Xeo

127

사용할 스마트 포인터 결정은 소유권 문제입니다 . 자원 관리와 관련하여 오브젝트 B 오브젝트 B의 수명을 제어하는 ​​경우 오브젝트 B를 소유 합니다. 예를 들어 멤버 변수의 수명이 오브젝트의 수명에 연결되어 있으므로 멤버 변수는 해당 오브젝트가 소유합니다. 객체의 소유 방식에 따라 스마트 포인터를 선택합니다.

소프트웨어 시스템에 대한 소유권은 소프트웨어 외부에서 생각할 때 소유권과 분리됩니다. 예를 들어, 사람이 자신의 집을 "소유"할 수 있지만 반드시 Person개체가 개체의 수명을 제어 할 수있는 것은 아닙니다 House. 이러한 실제 개념을 소프트웨어 개념과 접목시키는 것은 홀로 자신을 프로그래밍 할 수있는 확실한 방법입니다.


객체의 독점 소유권이있는 경우을 사용하십시오 std::unique_ptr<T>.

객체 소유권을 공유 한
경우 ...- 소유권주기가없는 경우을 사용하십시오 std::shared_ptr<T>.
-사이클이있는 경우 "방향"을 정의하고 std::shared_ptr<T>한 방향과 std::weak_ptr<T>다른 방향으로 사용 하십시오.

객체가 자신을 소유하지만 소유자가 없을 가능성이있는 경우 일반 포인터 T*(예 : 부모 포인터)를 사용하십시오.

객체가 자신을 소유하거나 존재를 보장하는 경우 reference를 사용하십시오 T&.


경고 : 스마트 포인터의 비용을 알고 있어야합니다. 메모리 또는 성능이 제한된 환경에서는 메모리를 관리하기 위해보다 수동적 인 방식으로 일반 포인터를 사용하는 것이 좋습니다.

가격:

  • 사용자 지정 삭제 기가있는 경우 (예 : 할당 풀을 사용하는 경우) 수동 삭제로 쉽게 피할 수있는 포인터 당 오버 헤드가 발생합니다.
  • std::shared_ptr복사시 참조 카운트 증가의 오버 헤드와 소멸 감소, 보류 된 오브젝트의 삭제와 함께 0 카운트 검사가 뒤 따릅니다. 구현에 따라 코드가 부풀려져 성능 문제가 발생할 수 있습니다.
  • 컴파일 시간 모든 템플릿과 마찬가지로 스마트 포인터는 컴파일 시간에 부정적인 영향을 미칩니다.

예 :

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

이진 트리는 부모를 소유하지 않지만 트리의 존재는 부모 (또는 nullptr루트) 의 존재를 의미 하므로 일반 포인터를 사용합니다. 이진 트리 (값 의미론 포함)는 하위 항목의 소유권 만 가지므로 하위 항목은 std::unique_ptr입니다.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

여기서리스트 노드는 다음리스트와 이전리스트를 소유하므로 방향을 정의하고 shared_ptr다음과 weak_ptrprev를 사용하여주기를 중단합니다.


3
이진 트리의 예를 위해 일부 사람들은 shared_ptr<BinaryTree>자녀와 weak_ptr<BinaryTree>부모 관계에 사용 하는 것이 좋습니다 .
David Rodríguez-dribeas

@ DavidRodríguez-dribeas : Tree에 값 의미가 있는지 여부에 따라 다릅니다. 소스 트리가 파괴 된 후에도 사람들이 외부에서 트리를 참조하려고한다면 공유 / 약한 포인터 콤보가 가장 좋습니다.
피터 알렉산더

객체가 당신을 소유하고 있고 존재한다고 보장되면 참조가 아닌 이유.
Martin York

1
참조를 사용하면 부모를 변경할 수 없으므로 디자인을 방해하거나 방해하지 않을 수 있습니다. 나무 균형을 잡는 데 방해가 될 것입니다.
Mooing Duck

3
+1이지만 첫 번째 줄에 "소유권"정의를 추가해야합니다. 나는 종종 그것이 좀 더 구체적인 영역의 의미에 대한 소유권이 아니라 대상의 삶과 죽음에 관한 것임을 분명히 진술해야한다는 것을 알게된다.
Klaim

19

unique_ptr<T>참조 횟수가 필요할 때를 제외하고 항상 사용하십시오 shared_ptr<T>( 이 경우에는 weak_ptr<T>참조주기를 방지하기 위해 매우 드문 경우 ). 거의 모든 경우에 양도 가능한 고유 소유권은 괜찮습니다.

원시 포인터 : 발생할 수있는 비 소유 포인팅 인 공변량 리턴이 필요한 경우에만 유용합니다. 그렇지 않으면 크게 유용하지 않습니다.

배열 포인터 : 결과 를 자동으로 호출 unique_ptr하는 전문화 기능 T[]delete[]있으므로 unique_ptr<int[]> p(new int[42]);예를 들어 안전하게 수행 할 수 있습니다 . shared_ptr여전히 커스텀 삭제 도구가 필요하지만 특별한 공유 또는 고유 한 배열 포인터는 필요하지 않습니다. 물론, 그러한 것들은 대개 std::vector어쨌든 가장 잘 대체됩니다 . 불행히도 shared_ptr배열 액세스 기능을 제공하지 않으므로 여전히 수동으로 호출해야 get()하지만 및 대신을 unique_ptr<T[]>제공합니다 . 어쨌든 자신을 점검해야합니다. 이 만드는 하지만 틀림없이 일반 장점없이 부스트 종속 차종, 약간 덜 사용자 친화적 인 및 수상자 다시.operator[]operator*operator->shared_ptrunique_ptrshared_ptr

범위가 지정된 포인터 : unique_ptr와 같이 관련이 없습니다 auto_ptr.

더 이상 아무것도 없습니다. 이동 의미가없는 C ++ 03에서는이 상황이 매우 복잡했지만 C ++ 11에서는 조언이 매우 간단합니다.

intrusive_ptr또는 같은 다른 스마트 포인터에는 여전히 사용됩니다 interprocess_ptr. 그러나, 그들은있어 매우 틈새 시장 및 일반적인 경우 완전히 불필요.


또한 반복에 대한 원시 포인터. 그리고 출력 매개 변수 버퍼의 경우 호출자가 버퍼를 소유합니다.
벤 Voigt

흠, 내가 읽은 방식은 공변량 수익과 비 소유 상황입니다. 교차점이 아닌 결합을 의미하는 경우 재 작성이 좋을 수 있습니다. 또한 반복은 특별히 언급 할 가치가 있다고 말합니다.
벤 Voigt

2
std::unique_ptr<T[]>operator[]대신 제공합니다 . 그래도 여전히 자신을 검사해야합니다. operator*operator->
Xeo

8

사용시기 unique_ptr:

  • 공장 방법
  • 포인터 인 멤버 (그림 포함)
  • stl 격리 자에 포인터 저장 (이동을 피하기 위해)
  • 큰 로컬 동적 객체 사용

사용시기 shared_ptr:

  • 여러 스레드에서 객체 공유
  • 일반적으로 객체 공유

사용시기 weak_ptr:

  • 일반적인 참조 역할을하는 큰지도 (예 : 모든 열린 소켓의지도)

자유롭게 편집하고 추가하십시오


나는 당신이 시나리오를 줄 때 실제로 당신의 대답을 더 좋아합니다.
Nicholas Humphrey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.