Programing

C ++에서 make_shared와 normal shared_ptr의 차이점

lottogame 2020. 4. 8. 07:37
반응형

C ++에서 make_shared와 normal shared_ptr의 차이점


std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

많은 Google 및 stackoverflow 게시물이 여기에 있지만 make_shared직접 사용하는 것보다 더 효율적인 이유를 이해할 수 없습니다 shared_ptr.

누군가 make_shared얼마나 효율적 인지 이해할 수 있도록 객체가 생성되고 작업이 수행되는 단계별 순서를 설명 할 수 있습니까 ? 위의 예를 참조로 제공했습니다.


차이점은 std::make_shared하나의 힙 할당 수행하는 반면 std::shared_ptr생성자를 호출하면 2를 수행 한다는 것 입니다.

힙 할당은 어디에서 발생합니까?

std::shared_ptr 두 엔티티를 관리합니다.

  • 제어 블록 (참조 횟수, 유형 소거 삭제 기 등의 메타 데이터 저장)
  • 관리되고있는 객체

std::make_shared제어 블록과 데이터 모두에 필요한 공간을 단일 힙 할당 계정으로 수행합니다. 다른 경우 new Obj("foo")에는 관리되는 데이터에 대한 힙 할당을 호출하고 std::shared_ptr생성자는 제어 블록에 대해 또 다른 힙 할당을 수행합니다.

자세한 정보 cppreference 에서 구현 정보를 확인하십시오 .

업데이트 I : 예외 안전

참고 (2019/08/30) : 함수 인수의 평가 순서가 변경되어 C ++ 17부터 문제가되지 않습니다. 특히 함수에 대한 각 인수는 다른 인수를 평가하기 전에 완전히 실행해야합니다.

OP가 예외 안전 측면에 대해 궁금해하는 것처럼 보이기 때문에 답변을 업데이트했습니다.

이 예를 고려하십시오.

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

C ++에서는 임의의 하위 표현식 평가 순서를 허용하므로 가능한 순서는 다음과 같습니다.

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

이제 2 단계에서 예외가 발생한다고 가정합니다 (예 : 메모리 부족 예외, Rhs생성자가 일부 예외 발생). 그런 다음 1 단계에서 할당 된 메모리가 손실됩니다. 메모리를 정리할 기회가 없었기 때문입니다. 여기서 문제의 핵심은 원시 포인터가 std::shared_ptr생성자에게 즉시 전달되지 않았다는 것 입니다.

이 문제를 해결하는 한 가지 방법은 이러한 임의의 순서가 발생하지 않도록 별도의 줄에서 수행하는 것입니다.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

이 문제를 해결하기 위해 선호되는 방법은 std::make_shared대신 사용하는 것입니다.

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

업데이트 II : 단점 std::make_shared

인용 케이시 의 의견 :

할당은 하나뿐이므로 제어 블록을 더 이상 사용하지 않을 때까지 포인트의 메모리를 할당 해제 할 수 없습니다. A weak_ptr는 제어 블록을 무기한으로 유지할 수 있습니다.

의 인스턴스가 weak_ptr제어 블록을 활성 상태로 유지하는 이유는 무엇 입니까?

weak_ptrs가 관리 대상 개체가 여전히 유효한지 확인 하는 방법이 있어야 합니다 (예 : for lock). shared_ptr제어 블록에 저장된 관리 대상 개체를 소유 한 수를 확인하여이를 수행합니다 . 그 결과 shared_ptr카운트와 weak_ptr카운트가 모두 0에 도달 할 때까지 제어 블록이 활성화 됩니다.

돌아가다 std::make_shared

이후 std::make_shared상기 제어 블록 및 관리 개체 모두 단일 힙 할당하게 제어 블록 메모리와 별도로 관리 오브젝트를 확보 할 수있는 방법이 없다. 제어 블록과 관리 대상 개체를 모두 해제 할 수있을 때까지 기다려야합니다.이 개체는 존재하지 shared_ptr않거나 weak_ptr살아 있지 않을 때까지 발생합니다 .

대신 제어 블록과 관리 객체를 통해 newshared_ptr생성자 를 통해 두 개의 힙 할당을 수행했다고 가정합니다 . 그런 다음 활성이없는 경우 관리 대상 객체 (이전)에 shared_ptr대한 메모리를 해제하고 활성 이없는 경우 제어 블록 (이후에)에 대한 메모리를 해제합니다 weak_ptr.


공유 포인터는 객체 자체와 참조 횟수 및 기타 하우스 키핑 데이터를 포함하는 작은 객체를 모두 관리합니다. make_shared이 두 가지를 모두 유지하기 위해 단일 메모리 블록을 할당 할 수 있습니다. 포인터에서 이미 할당 된 객체에 대한 공유 포인터를 구성하려면 참조 카운트를 저장하기 위해 두 번째 블록을 할당해야합니다.

이러한 효율성뿐만 아니라, 사용 make_shared한다는 것은 new포인터 를 처리 할 필요가없고 원시 포인터를 전혀 처리하지 않아도 예외 안전성 이 향상 된다는 것입니다 . 객체를 할당 한 후 스마트 포인터에 할당하기 전에 예외를 던질 가능성이 없습니다.


이미 언급 한 것 외에 두 가지 가능성이 다른 또 다른 경우가 있습니다. 비공개 생성자 (보호 또는 개인)를 호출해야하는 경우 make_shared에서 액세스 할 수 없을 수 있습니다. .

class A
{
public:

    A(): val(0){}

    std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
    // Invalid because make_shared needs to call A(int) **internally**

    std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
    // Works fine because A(int) is called explicitly

private:

    int val;

    A(int v): val(v){}
};

shared_ptr로 제어되는 객체에 특별한 메모리 정렬이 필요한 경우 make_shared를 사용할 수 없지만 사용하지 않는 유일한 이유라고 생각합니다.


Shared_ptr: 두 개의 힙 할당을 수행합니다.

  1. 제어 블록 (참조 횟수)
  2. 관리중인 개체

Make_shared: 하나의 힙 할당 만 수행

  1. 제어 블록 및 객체 데이터.

std :: make_shared에 한 가지 문제가 있습니다. 개인 / 보호 생성자를 지원하지 않습니다.


할당에 소요되는 효율성 및 우려 시간에 대해 아래에서이 간단한 테스트를 수행하여이 두 가지 방법 (한 번에 하나씩)을 통해 많은 인스턴스를 작성했습니다.

for (int k = 0 ; k < 30000000; ++k)
{
    // took more time than using new
    std::shared_ptr<int> foo = std::make_shared<int> (10);

    // was faster than using make_shared
    std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}

문제는 make_shared를 사용하는 것이 new를 사용하는 것보다 두 배가 걸렸습니다. 따라서 new를 사용하면 make_shared를 사용하는 대신 두 개의 힙 할당이 있습니다. 어쩌면 이것은 어리석은 테스트이지만 make_shared를 사용하는 것이 new를 사용하는 것보다 시간이 더 걸린다는 것을 보여주지 않습니까? 물론, 나는 단지 사용 된 시간에 대해서만 이야기하고 있습니다.

참고 : https://stackoverflow.com/questions/20895648/difference-in-make-shared-and-normal-shared-ptr-in-c

반응형