raw, weak_ptr, unique_ptr, shared_ptr 등… 현명하게 선택하는 방법?


33

C ++에는 많은 포인터가 있지만 5 년 정도면 정직하게 C ++ 프로그래밍 (구체적으로 Qt 프레임 워크)에서는 오래된 원시 포인터 만 사용합니다.

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

다른 "스마트 한"포인터가 많이 있다는 것을 알고 있습니다.

// shared pointer:
shared_ptr<SomeKindofObject> Object;

// unique pointer:
unique_ptr<SomeKindofObject> Object;

// weak pointer:
weak_ptr<SomeKindofObject> Object;

그러나 나는 그들과 함께 무엇을하고 그들이 원시 포인터와 비교하여 나에게 무엇을 제공 할 수 있는지에 대한 사소한 생각이 없습니다.

예를 들어이 클래스 헤더가 있습니다.

#ifndef LIBRARY
#define LIBRARY

class LIBRARY
{
public:
    // Permanent list that will be updated from time to time where
    // each items can be modified everywhere in the code:
    QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings; 
private:
    // Temporary reader that will read something to put in the list
    // and be quickly deleted:
    QSettings *_reader;
    // A dialog that will show something (just for the sake of example):
    QDialog *_dialog;
};

#endif 

이것은 철저하지는 않지만 이러한 세 가지 포인터 각각에 대해 "원시"로 남겨 두거나 더 적절한 것을 사용해야합니까?

그리고 두 번째로, 고용주가 코드를 읽을 경우, 내가 어떤 종류의 포인터를 사용하는지에 대해 엄격합니까?


이 주제는 너무 적합합니다. 이것은 2008 년 이었다. 그리고 여기에 어떤 종류의 포인터를 사용해야합니까? . 더 나은 성냥을 찾을 수 있다고 확신합니다. 이것들은 내가 처음 본 것입니다
sehe

이것은이 클래스의 개념적 의미 / 의도에 관한 것이기 때문에 그들의 행동과 구현에 대한 기술적 세부 사항에 관한 것이기 때문에이 경계선입니다. 받아 들여진 대답은 전자에 의존하기 때문에, 이것이 그 SO 질문의 "PSE 버전"이되어서 기쁩니다.
Ixrec

답변:


70

"원시"포인터는 관리되지 않습니다. 즉, 다음 줄 :

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

... delete적절한 시간에 동반자 가 실행되지 않으면 메모리가 누출 됩니다.

auto_ptr

이러한 경우를 최소화하기 위해 std::auto_ptr<>도입되었습니다. 그러나 2011 년 표준 이전의 C ++의 한계로 인해 auto_ptr메모리 누수는 여전히 쉽습니다 . 그러나 다음과 같은 제한된 경우에는 충분합니다.

void func() {
    std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
    // do some work
    // will not leak if you do not copy sKOO_ptr.
}

가장 약한 사용 사례 중 하나는 컨테이너입니다. 의 사본 auto_ptr<>이 만들어지고 이전 사본이 신중하게 재설정되지 않으면 컨테이너가 포인터를 삭제하고 데이터를 잃을 수 있기 때문 입니다.

unique_ptr

C ++ 11은 std::unique_ptr<>다음과 같은 대안을 도입했습니다 .

void func2() {
    std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());

    func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}

이러한 a unique_ptr<>는 기능간에 전달 될 때도 올바르게 정리됩니다. 포인터의 "소유권"을 의미 적으로 표현하여이를 수행합니다. "소유자"가이를 정리합니다. 따라서 컨테이너에 사용하기에 이상적입니다.

std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();

달리가 auto_ptr<>, unique_ptr<>때 여기에 잘 행동하고, vector크기 변경은 객체의 어느 것도 실수 동안 삭제되지 않고 vector복사합니다 백업 저장소를.

shared_ptrweak_ptr

unique_ptr<>유용하지만 확실하지만 코드베이스의 두 부분이 동일한 객체를 참조하고 포인터를 복사하여 적절한 정리를 보장하려는 경우가 있습니다. 예를 들어, 다음을 사용할 때 트리는 다음과 같습니다 std::shared_ptr<>.

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

이 경우 루트 노드의 여러 복사본을 유지할 수 있으며 루트 노드의 모든 복사본이 삭제되면 트리가 올바르게 정리됩니다.

이것은 각각 shared_ptr<>객체에 대한 포인터뿐만 아니라 shared_ptr<>동일한 포인터를 참조하는 모든 객체 의 참조 카운트를 유지 하기 때문에 작동합니다 . 새로운 것이 만들어지면 카운트가 올라갑니다. 하나가 파괴되면 카운트가 내려갑니다. 카운트가 0에 도달하면 포인터는 deleted입니다.

이중 링크 구조는 순환 참조로 끝납니다. parent트리에 포인터 를 추가하고 싶다고 가정 해보십시오 Node.

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

이제를 제거 Node하면 순환 참조가 있습니다. delete참조 카운트가 0이 아니기 때문에 절대 d가되지 않습니다.

이 문제를 해결하려면 다음을 사용하십시오 std::weak_ptr<>.

template<class T>
struct Node {
    T value;
    std::weak_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

이제는 제대로 작동하며 노드를 제거해도 부모 노드에 대한 참조가 고정되지 않습니다. 그러나 나무를 걷는 것은 조금 더 복잡합니다.

std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();

이런 식으로 노드에 대한 참조를 잠글 수 있으며 작업하는 동안 노드를 유지하고 있기 때문에 사라지지 않도록 합리적으로 보장 할 shared_ptr<>수 있습니다.

make_sharedmake_unique

지금, 거기에 약간의 문제가 있습니다 shared_ptr<>unique_ptr<>그 해결해야한다. 다음 두 줄에 문제가 있습니다.

foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());

경우 thrower()예외를 발생 두 라인 메모리 누출. 그리고 그 이상으로, shared_ptr<>참조 카운트가 가리키는 객체에서 멀리 떨어져 있으며 이는 두 번째 할당을 의미 할 수 있습니다 ). 일반적으로 바람직하지 않습니다.

이 문제를 해결하기 위해 C ++ 11이 제공 std::make_shared<>()하고 C ++ 14가 제공 std::make_unique<>()합니다.

foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());

이제 두 경우 모두 thrower()예외가 발생 하더라도 메모리 누수가 발생하지 않습니다. 보너스로, make_shared<>()그 참조 카운트를 만들 수있는 기회가 동일한 메모리 공간에서 당신에게 예외 안전 보장을 제공하면서, 모두 빠르게 할 수 있고 메모리의 몇 바이트를 저장할 수 있습니다 관리되는 객체로를!

Qt에 대한 메모

그러나 C ++ 11 이전의 컴파일러를 지원해야하는 Qt에는 자체 가비지 수집 모델이 있습니다. 많은 사람들 QObject이 사용자가 필요로하지 않고 제대로 파괴되는 메커니즘을 가지고 delete있습니다.

QObjectC ++ 11 관리 포인터로 관리 할 때 s가 어떻게 작동 하는지 알지 못 하므로 shared_ptr<QDialog>좋은 생각 이라고 말할 수 없습니다 . Qt에 대한 충분한 경험이 없지만 Qt5 가이 사용 사례에 맞게 조정 되었다고 생각 합니다.


1
@Zilators : Qt에 대한 추가 의견을 참고하십시오. 세 가지 포인터를 모두 관리해야하는지에 대한 귀하의 질문에 대한 답변은 Qt 객체가 제대로 작동하는지 여부에 따라 다릅니다.
greyfade

2
"포인터를 유지하기 위해 별도의 할당을 수행"? 아니요, unique_ptr은 추가 항목을 할당하지 않으며 shared_ptr 만 reference-count + allocator-object를 할당해야합니다. "두 줄 모두 메모리 누수"? 아니, 단지 나쁜 행동의 보장조차 할 수 없습니다.
중복 제거기

1
@ 중복 제거기 : 내 문구가 명확하지 않아야 합니다. ed 개체 shared_ptr와 별도의 개체-별도 할당 new입니다. 그들은 다른 위치에 존재합니다. make_shared캐시를 같은 위치에 배치 할 수있어 캐시 위치가 향상됩니다.
greyfade

2
@greyfade : 노 노노. shared_ptr객체입니다. 그리고 객체를 관리하려면 (참조 카운트 (약 + 강한) + 구축함) 객체를 할당해야합니다. make_shared그와 관리 대상 개체를 한 조각으로 할당 할 수 있습니다. unique_ptr스마트 포인터가 객체를 항상 소유하고 있는지 확인하는 것 외에는 해당 이점이 없습니다. 옆으로, 기본 객체 shared_ptr소유하고 a 를 나타내 nullptr거나 null이 아닌 포인터를 소유하고 나타내지 않는 a를 가질 수 있습니다.
중복 제거기

1
나는 그것을 보았고, 무엇을하는지에 대한 일반적인 혼란이있는 것 같습니다 shared_ptr: 1. 일부 객체의 소유권을 공유합니다 (삭제자가 아니라 약하고 강력한 참조 카운트를 갖는 내부 동적 할당 객체로 나타남) . 2. 포인터가 들어 있습니다. 이 두 부분은 독립적입니다. make_unique그리고 make_shared둘 다 할당 된 객체가 스마트 포인터에 안전하게 배치되도록합니다. 또한 make_shared소유권 개체와 관리되는 포인터를 함께 할당 할 수 있습니다.
중복 제거기
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.