반원을위한 스마트 포인터 사용


159

C ++ 11의 클래스 멤버로 스마트 포인터의 사용법을 이해하는 데 문제가 있습니다. 나는 스마트 포인터에 대해 많이 읽었으며 나는 방법을 이해하는 것 같아요 unique_ptrshared_ptr/ weak_ptr일반적으로 작업을. 내가 이해하지 못하는 것은 실제 사용법입니다. 모든 사람들 unique_ptr이 거의 항상가는 길로 사용 하는 것이 좋습니다 . 그러나 어떻게 이런 식으로 구현할 것입니까?

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

포인터를 스마트 포인터로 바꾸고 싶다고합시다. unique_ptr때문에 A 가 작동하지 getDevice()않습니까? 그래서 내가 사용하는 시간 shared_ptrweak_ptr? 사용 방법이 unique_ptr없습니까? shared_ptr정말 작은 범위에서 포인터를 사용하지 않으면 대부분의 경우처럼 보입니다 .

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

갈 길이예요? 매우 감사합니다!


4
수명, 소유권 및 가능한 null에 대해 명확하게 알 수 있습니다. 예를 들어 device의 생성자에 전달한 settings후에도 호출 범위에서 또는 참조를 통해서만 참조 할 수 settings있습니까? 후자의 경우 unique_ptr유용합니다. 또한,의 반환 값은 어디 시나리오를 가지고 할 getDevice()것입니다 null. 그렇지 않은 경우 참조를 반환하십시오.
Keith

2
예, shared_ptr8/10의 경우 a 가 맞습니다. 다른 2/10 사이에 분할 unique_ptr하고 weak_ptr. 또한 weak_ptr일반적으로 순환 참조를 끊는 데 사용됩니다. 귀하의 사용법이 올바른지 확실하지 않습니다.
Collin Dauphinee

2
우선, device데이터 멤버에 대해 어떤 소유권을 원 하십니까? 먼저 결정해야합니다.
juanchopanza

1
좋아, 나는 unique_ptr더 이상 필요하지 않다는 것을 알면 호출자 대신 대신 사용할 수 있고 생성자를 호출 할 때 소유권을 포기할 수 있음을 이해한다 . 그러나 Settings클래스 디자이너로서 호출자가 참조를 유지 하려는지 여부를 알 수 없습니다. 어쩌면 장치는 여러 곳에서 사용될 것입니다. 좋아, 아마 그게 당신의 요점 일 것입니다. 이 경우 단독 소유자가 아니므로 shared_ptr을 사용할 것입니다. 그리고 : 똑똑한 점은 포인터를 대체하지만 참조는 대체하지 않습니다.
michaelk

this-> device = 장치; 초기화 목록도 사용하십시오.
Nils

답변:


202

unique_ptr때문에 A 가 작동하지 getDevice()않습니까?

반드시 그런 것은 아닙니다. 여기서 중요한 것은 귀하 의 적절한 소유권 정책 을 결정하는 것입니다Device 객체에 , 즉 (스마트) 포인터가 가리키는 객체의 소유자가 될 사람 것입니다.

그것은의 인스턴스가 될 것입니다 Settings개체 혼자 ? 때 Device개체를 자동으로 파괴해야합니까Settings 객체가 파괴됩니다, 또는 해당 개체 오래 살 것인가?

첫 번째 경우에는 std::unique_ptr필요한 것입니다.Settings 뾰족한 물체의 유일한 (독특한) 소유자, 그 파괴에 대한 책임이있는 유일한 개체를.

이 가정 하에서는 getDevice()간단한 관찰 포인터를 반환해야합니다 (관찰 포인터는 뾰족한 객체를 유지하지 않는 포인터입니다). 가장 간단한 종류의 관찰 포인터는 원시 포인터입니다.

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ 참고 1 : 모든 사람들이 원시 포인터가 나쁘고 안전하지 않으며 위험하다고 계속 말할 때 왜 내가 원시 포인터를 사용하는지 궁금 할 것입니다. 사실, 그 귀중한 경고이지만, 올바른 문맥에 넣어하는 것이 중요하다 : 원시 포인터가 나쁜 수동으로 메모리 관리를 수행하는 데 사용하는 경우 즉, 할당하고,를 통해 객체를 할당 해제 new하고 delete. 순전히 참조 의미론을 달성하고 비 소유, 포인터를 관찰하기위한 수단으로 사용될 때, 댕글 링 포인터를 역 참조하지 않도록주의해야한다는 점을 제외하고는 원시 포인터에는 본질적으로 위험한 것은 없습니다. - 종료 참고 1 ]

[ 비고 2 : 주석에서 나타난 바와 같이, 소유권이 고유 하고 소유 한 객체가 항상 존재 함을 보장하는 (즉, 내부 데이터 멤버 device는 절대로 존재하지 않을 nullptr) 특별한 경우에 , 함수 getDevice()는 (그리고 아마도) 포인터가 아닌 참조를 반환합니다. 이 사실이지만 내가 한 경우에 일반화 할 수있는 짧은 대답으로이 의미하기 때문에, 여기 원시 포인터를 반환하기로 결정 devicenullptr, 원시 포인터가 긴 하나가 사용하지 않는으로 OK 있음을 보여 수동 메모리 관리. - 종료 참고 2 ]


물론 Settings객체가 장치의 독점 소유권을 가져서는 안되는 경우 상황은 근본적으로 다릅니다 . 예를 들어, Settings물체의 파괴가 뾰족한 Device물체 의 파괴를 의미하지 않아야하는 경우에 해당 될 수 있습니다.

이것은 프로그램 설계자로서 귀하 만 알 수있는 것입니다. 당신이 제공 한 예에서, 이것이 사실인지 아닌지를 말하기는 어렵습니다.

이해하는 데 도움을주기 Settings위해 Device수동 관찰자가 아닌 객체에 대한 포인터를 보유하고 있는 한 객체를 생존 상태로 유지할 수있는 다른 객체가 있는지 여부를 스스로에게 물어볼 수 있습니다 . 그런 경우가 실제로 있다면, 당신은 필요 공유 소유권 정책이 무엇인가, std::shared_ptr이벤트를 :

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

공지 사항, weak_ptr이다 관찰 포인터가 아닌 소유 포인터 - 뾰족한 물체에 다른 모든 소유 포인터가 범위 밖으로 이동하는 경우 즉, 살아 뾰족한 물체를 유지하지 않습니다.

weak_ptr일반 원시 포인터보다 장점 weak_ptr매달려 있는지 여부를 안전하게 알 수 있다는 것입니다 (즉, 유효한 객체를 가리키는 지 또는 객체가 원래 가리키는 객체인지 여부). 이것은 객체 에서 expired()멤버 함수를 호출하여 수행 할 수 있습니다 weak_ptr.


4
@LKK : 그렇습니다. A weak_ptr는 항상 원시 관측 포인터의 대안입니다. 참조 해제하기 전에 매달려 있는지 확인할 수 있기 때문에 어느 정도 안전하지만 약간의 오버 헤드가 있습니다. 매달려있는 포인터를 역 참조하지 않을 것을 쉽게 보장 할 수 있다면, 원시 포인터를 관찰하는 것이 좋습니다
Andy Prowl

6
첫 번째 경우, 아마도 getDevice()참조를 반환하는 것이 더 좋을 것입니다 . 따라서 호출자는 확인하지 않아도됩니다 nullptr.
vobject

5
@chico : 무슨 뜻인지 잘 모르겠습니다. 호출 된 auto myDevice = settings.getDevice()형식의 새 인스턴스를 만들고 반환 하는 참조로 참조 된 인스턴스에서 복사 구성 합니다. 당신이 원하는 경우 참조로, 당신은 할 필요 . 그래서 내가 빠진 것이 아니라면, 우리는 우리가 사용하지 않은 것과 같은 상황으로 돌아 왔습니다 . DevicemyDevicegetDevice()myDeviceauto& myDevice = settings.getDevice()auto
Andy Prowl

2
@Purrformance : 객체의 소유권을 포기하고 싶지 않기 때문에 unique_ptr클라이언트 에게 수정 가능 항목 을 전달하면 클라이언트가 객체 에서 이동할 가능성이 생겨 소유권 을 획득하고 널 (독특한) 포인터로 남겨 둡니다.
Andy Prowl

7
@Purformance : (고객이 미친 과학자가 아니라면) 고객이 움직이지 못하게 할 수는 있지만 const_cast개인적으로는 그렇게하지 않을 것입니다. 구현 세부 사항, 즉 소유권이 고유하고를 통해 실현된다는 사실을 공개합니다 unique_ptr. 소유권을 전달 / 반환하려면 스마트 포인터를 전달 / 반환해야합니다 ( unique_ptr또는 shared_ptr소유권의 종류에 따라). 소유권을 전달 / 반환하지 않으려는 경우 const에는 인수가 널 (NULL)이 될 수 있는지 여부에 따라 (적절하게 규정 된 ) 포인터 또는 참조를 사용하십시오.
Andy Prowl

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptr참조 루프에만 사용됩니다. 종속성 그래프는 순환 방향 그래프 여야합니다. 공유 포인터에는 2 개의 참조 카운트가 있습니다 : 1은 shared_ptrs, 1은 모든 포인터 ( shared_ptrweak_ptr)입니다. 모든 shared_ptr을 제거하면 포인터가 삭제됩니다. 포인터에서 필요한 경우 weak_ptr, lock존재하는 경우, 포인터를 가져 오는 데 사용되어야한다.


따라서 대답을 올바르게 이해하면 스마트 포인터가 원시 포인터를 대체하지만 반드시 참조는 아닙니다.
michaelk

실제로 두 개의 참조 횟수가 shared_ptr있습니까? 이유를 설명해 주시겠습니까? 내가 이해하는 한, 객체를 조작 할 때 (기본 객체가 여전히 존재하는 경우) weak_ptr단순히 새로 만들기 때문에 계산할 필요가 없습니다 shared_ptr.
Björn Pollex

@ BjörnPollex : 나는 당신을 위해 짧은 예를 만들었습니다 : link . 나는 복사 생성자와 and 만 모든 것을 구현하지는 않았다 lock. 부스트 버전은 참조 카운팅시 스레드 안전합니다 ( delete한 번만 호출).
Naszta

@ Naszta : 귀하의 예는 두 개의 참조 카운트를 사용하여 이것을 구현할 수 있음 을 보여 주지만, 대답은 이것이 필요하다고 제안합니다 . 당신의 대답에서 이것을 명확히 해 주시겠습니까?
Björn Pollex

1
@ BjörnPollex, weak_ptr::lock()개체가 만료되었는지 확인하려면 첫 번째 참조 카운트와 개체에 대한 포인터가 포함 된 "제어 블록"을 검사해야하므로 weak_ptr아직 사용중인 개체 가있는 동안 제어 블록을 파괴해서는 안됩니다 . 따라서 weak_ptr객체 의 수를 추적해야합니다. 이것이 두 번째 참조 카운트가하는 것입니다. 첫 번째 참조 카운트가 0으로 떨어지면 객체가 파괴되고, 두 번째 참조 카운트가 0으로 떨어지면 제어 블록이 파괴됩니다.
Jonathan Wakely
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.