소유권과 스마트 포인터 (Ownership and Smart Pointers)¶
동적으로 할당된 개체에 대한 단일 고정 소유자를 선호합니다. 스마트 포인터를 사용하여 소유권을 이전하는 것을 선호합니다.
"소유권"은 동적으로 할당된 메모리(및 기타 리소스)를 관리하기 위한 기록 기술입니다. 동적으로 할당된 개체의 소유자는 해당 개체가 더 이상 필요하지 않을 때 삭제되도록 하는 역할을 담당하는 개체 또는 함수입니다. 소유권이 공유되는 경우도 있는데, 이 경우 일반적으로 마지막 소유자가 소유권을 삭제할 책임이 있습니다. 소유권이 공유되지 않는 경우에도 한 코드에서 다른 코드로 이전될 수 있습니다.
"스마트" 포인터는 예를 들어 * 및 -> 연산자를 오버로드하여 포인터처럼 작동하는 클래스입니다. 일부 스마트 포인터 유형을 사용하면 소유권 장부를 자동화하여 이러한 책임을 충족할 수 있습니다. std::unique_ptr은 동적으로 할당된 객체의 독점적 소유권을 표현하는 스마트 포인터 유형입니다. std::unique_ptr이 범위를 벗어나면 객체가 삭제됩니다. 복사할 수는 없지만 소유권 이전을 나타내기 위해 이동할 수는 있습니다. std::shared_ptr은 동적으로 할당된 객체의 공유 소유권을 표현하는 스마트 포인터 유형입니다. std::shared_ptr 은 복사될 수 있습니다. 개체의 소유권은 모든 복사본에서 공유되며 마지막 std::shared_ptr이 삭제되면 개체가 삭제됩니다.
- 일종의 소유권 논리 없이 동적으로 할당된 메모리를 관리하는 것은 사실상 불가능합니다.
- 객체의 소유권을 이전하는 것은 객체를 복사하는 것보다 저렴할 수 있습니다(복사가 가능한 경우).
- 소유권을 이전하는 것은 포인터나 참조를 '빌리는' 것보다 더 간단할 수 있습니다. 두 사용자 간의 개체 수명을 조정할 필요성이 줄어들기 때문입니다.
- 스마트 포인터는 소유권 논리를 명시적이고 자체 문서화하며 명확하게 만들어 가독성을 향상시킬 수 있습니다.
- 스마트 포인터는 수동 소유권 장부를 없애고 코드를 단순화하며 대규모 오류를 배제할 수 있습니다.
-
const 객체의 경우 공유 소유권은 전체 복사에 대한 간단하고 효율적인 대안이 될 수 있습니다.
-
소유권은 포인터(스마트 또는 일반)를 통해 표시되고 전송되어야 합니다. 포인터 의미론은 특히 API에서 값 의미론보다 더 복잡합니다. 소유권뿐만 아니라 별칭, 수명, 변경 가능성 등의 문제도 걱정해야 합니다.
- 가치 의미론의 성능 비용은 종종 과대평가되므로 소유권 이전의 성능 이점이 가독성 및 복잡성 비용을 정당화하지 못할 수 있습니다.
- 소유권을 이전하는 API는 클라이언트를 단일 메모리 관리 모델로 강제합니다.
- 스마트 포인터를 사용하는 코드는 리소스 릴리스가 발생하는 위치에 대해 덜 명확합니다.
- std::unique_ptr은 이동 의미 체계를 사용하여 소유권 이전을 표현하는데, 이는 복잡할 수 있으며 일부 프로그래머에게 혼란을 줄 수 있습니다.
- 공유 소유권은 신중한 소유권 설계에 대한 매력적인 대안이 될 수 있으며 시스템 설계를 난독화할 수 있습니다.
- 공유 소유권을 사용하려면 런타임 시 명시적인 장부가 필요하므로 비용이 많이 들 수 있습니다.
- 어떤 경우에는(예: 순환 참조) 공유 소유권이 있는 객체가 삭제되지 않을 수도 있습니다.
- 스마트 포인터는 일반 포인터를 완벽하게 대체할 수 없습니다.
동적 할당이 필요한 경우 이를 할당한 코드로 소유권을 유지하는 것이 좋습니다. 다른 코드에서 개체에 액세스해야 하는 경우 복사본을 전달하거나 소유권을 이전하지 않고 포인터나 참조를 전달하는 것이 좋습니다. 소유권 이전을 명시적으로 지정하려면 std::unique_ptr을 사용하는 것이 좋습니다. 예를 들어:
특별한 이유 없이 공유 소유권을 사용하도록 코드를 설계하지 마세요. 그러한 이유 중 하나는 비용이 많이 드는 복사 작업을 피하는 것이지만, 성능상의 이점이 크고 기본 개체가 변경 불가능한 경우에만 이 작업을 수행해야 합니다(예: std::shared_ptr
std::auto_ptr 을 사용하지 마세요. 대신 std::unique_ptr 을 사용하세요.
이해하기 쉽게 설명하기¶
핵심: 단일 소유자를 선호, 소유권 이전은 스마트 포인터로¶
동적 할당 객체에는 한 명의 명확한 소유자를 두는 것을 선호하세요. 소유권을 넘겨야 한다면 std::unique_ptr로 그 의도를 코드에 드러내세요.
std::unique_ptr<Foo> FooFactory(); // 소유권을 호출자에게 넘김
void FooConsumer(std::unique_ptr<Foo> ptr); // 소유권을 넘겨받음
기본 원칙¶
- 동적 할당이 필요하면, 할당한 코드가 소유권을 유지하세요.
- 다른 코드가 객체에 접근만 해야 한다면, 소유권을 넘기지 말고 복사본이나 포인터·참조를 전달하세요.
- 소유권 이전을 표현할 때만
std::unique_ptr을 쓰세요.
공유 소유권(shared_ptr)은 신중히¶
std::shared_ptr은 마지막 소유자가 사라질 때 객체를 지웁니다. 편리하지만 런타임 장부 비용이 들고, 순환 참조 시 객체가 안 지워질 수 있으며, 설계를 모호하게 만듭니다. 비싼 복사를 피하고 이득이 크며 대상이 불변일 때(std::shared_ptr<const Foo>)만 쓰세요.
금지¶
std::auto_ptr은 쓰지 마세요. std::unique_ptr을 쓰세요.