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


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


4
더 효율적이지 않습니다. 이것을 사용하는 이유는 예외 안전을위한 것입니다.
Yuushi

일부 기사에 따르면 일부 건설 오버 헤드를 피할 수 있습니다. 더 자세히 설명해 주시겠습니까?
Anup Buchke

16
@Yuushi : 예외 안전은 그것을 사용해야하는 좋은 이유이지만 더 효율적입니다.
마이크 시모어

3
32:15는 위의 비디오에서 도움이된다면 시작합니다.
chris

4
사소한 코드 스타일의 장점 : make_shared당신이 쓸 수 auto p1(std::make_shared<A>())있고 p1은 올바른 유형을 가질 것입니다.
Ivan Vergiliev

답변:


333

차이점은 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

인용 Casey 의 의견 :

할당은 하나뿐이므로 제어 블록을 더 이상 사용하지 않을 때까지 포인트의 메모리를 할당 해제 할 수 없습니다. 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.


53
작은 코너 케이스 단점도 언급하는 것이 좋습니다 make_shared. 할당이 하나뿐이므로 제어 블록을 더 이상 사용하지 않을 때까지 포인트의 메모리를 할당 해제 할 수 없습니다. A weak_ptr는 제어 블록을 무기한으로 유지할 수 있습니다.
Casey

14
보다 스타일리시 한 또 다른 요점은 다음 make_shared과 같습니다. 사용 하고 make_unique일관되게 사용하는 경우 원시 포인터를 소유하지 않으며 모든 경우를 new코드 냄새로 취급 할 수 있습니다 .
Philipp

6
하나가 있다면 shared_ptr,없고 weak_ptr의 호출 reset()shared_ptr예하면 제어 블록을 삭제합니다. 그러나 이것은 상관없이 make_shared사용 되었는지 여부 입니다. 사용 make_shared하면 관리 대상 개체에 할당 된 메모리 수명이 연장 될 수 있으므로 차이가 있습니다. 때 shared_ptr카운트가 0 안타, 관리되는 개체에 대한 소멸자는 관계없이 호출됩니다 make_shared,하지만 경우 메모리에만 수행 할 수 있습니다 확보 make_shared했다 되지 사용. 이것이 더 명확 해지기를 바랍니다.
mpark

4
make_shared가 제어 블록을 더 작은 포인터로 만들 수있는 "우리가 사는 곳을 알고 있습니다"최적화를 활용할 수 있다는 점도 언급 할 가치가 있습니다. (자세한 내용 은 약 12 ​​분에 Stephan T. Lavavej의 GN2012 프리젠 테이션 을 참조하십시오 .) make_shared는 할당을 피할뿐만 아니라 총 메모리를 덜 할당합니다.
KnowItAllWannabe

1
@HannaKhalil : 이것은 아마도 당신이 찾고있는 것의 영역일까요? melpon.org/wandbox/permlink/b5EpsiSxDeEz8lGH
mpark

26

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

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


2
첫 번째 요점을 올바르게 이해했습니다. 예외 안전에 대한 두 번째 요점을 정교하게 설명하거나 링크를 제공 할 수 있습니까?
Anup Buchke

22

이미 언급 한 것 외에 두 가지 가능성이 다른 또 다른 경우가 있습니다. 비공개 생성자 (보호 또는 개인)를 호출해야하는 경우 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){}
};

나는이 정확한 문제에 부딪 치고 사용하기로 결정했습니다 . new그렇지 않으면 사용 했을 것 make_shared입니다. 여기에 관련 질문이 있습니다 : stackoverflow.com/questions/8147027/… .
jigglypuff

6

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


2
make_shared가 부적절한 두 번째 상황은 사용자 정의 삭제기를 지정하려는 경우입니다.
KnowItAllWannabe

5

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


3

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

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

Make_shared: 하나의 힙 할당 만 수행

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

0

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

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를 사용하는 것보다 시간이 더 걸린다는 것을 보여주지 않습니까? 물론, 나는 단지 사용 된 시간에 대해서만 이야기하고 있습니다.


4
그 테스트는 다소 의미가 없습니다. 최적화가 이루어진 릴리스 구성에서 테스트가 수행 되었습니까? 또한 모든 아이템이 즉시 해제되므로 현실적이지 않습니다.
Phil1970

0

mr mpark의 답변에서 예외 안전 부분은 여전히 ​​유효한 관심사입니다. shared_ptr <T> (new T)와 같은 shared_ptr을 작성할 때 새 T는 성공할 수 있지만 shared_ptr의 제어 블록 할당은 실패 할 수 있습니다. 이 시나리오에서는 shared_ptr이 해당 위치에서 작성된 것을 알 수있는 방법이없고이를 삭제해도 안전하므로 새로 할당 된 T가 누출됩니다. 아니면 뭔가 빠졌습니까? 함수 매개 변수 평가에 대한 엄격한 규칙이 어떤 식 으로든 도움이되지 않는다고 생각합니다 ...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.