참조 또는 값으로 shared_ptr을 전달해야합니까?


270

함수가 shared_ptr(부스트 또는 C ++ 11 STL에서) 가져 가면 전달합니까?

  • const 참조로 : void foo(const shared_ptr<T>& p)

  • 또는 값으로 : void foo(shared_ptr<T> p)?

나는 그것이 더 빠를 것이라고 생각하기 때문에 첫 번째 방법을 선호합니다. 그러나 이것이 실제로 가치가 있습니까? 아니면 추가 문제가 있습니까?

선택해야 할 이유를 알려 주시거나 문제가 해결되지 않는 이유를 말씀해주십시오.


14
문제는 이것들이 동등하지 않다는 것입니다. 참조 버전은 "일부 별칭을 지정 shared_ptr하고 원하면 변경할 수 있습니다."라고 비명을 지르며 , 값 버전은 "복사 할 shared_ptr것이므로 변경할 수는 없습니다. ) const-reference 매개 변수는 실제 솔루션으로 "일부 별칭 shared_ptr을 지정하고 변경하지 않겠다고 약속합니다."(
값별

2
이봐, 난 반원 반환 에 대한 당신의 의견에 관심이있을 것 shared_ptr입니다. const-refs로합니까?
Johannes Schaub-litb

세번째 가능성은 std :: move ()를 C ++ 0x와 함께 사용하는 것입니다. 이것은 shared_ptr
Tomaka17

@Johannes : 복사 / 참조 계산을 피하기 위해 const 참조로 반환합니다. 그런 다음 다시 원시적이 아닌 한 모든 멤버를 const-reference로 반환합니다.
GManNickG

답변:


229

이 질문은 C ++ 및 Beyond 2011의 Ask Us Anything 세션에서 Scott, Andrei 및 Herb가 논의하고 답변했습니다 . 4시 34분에서 시계 에 대한 성능 및 정확성 .shared_ptr

곧, 목표가 객체의 소유권을 공유하는 것이 아니라면 (예를 들어, 다른 데이터 구조 또는 다른 스레드간에) 목표를 공유하지 않는 한, 가치를 지나갈 이유가 없습니다 .

위에 링크 된 토크 비디오에서 Scott Meyers가 설명한대로 이동 최적화 할 수 없다면 실제 C ++ 버전과 관련이 있습니다.

이 토론에 대한 주요 업데이트는 GoingNative 2012 컨퍼런스의 대화식 패널 : 문의하십시오! 특히 22:50 부터 시청할 가치가 있습니다.


5
다음과 같이하지만 값으로 전달하는 저렴 : stackoverflow.com/a/12002668/128384는 상기 shared_ptr의 구성원의 할 것입니다 생성자 인자 등을 위해 적어도 (뿐만 아니라 고려되어서는 안된다는 클래스)?
stijn

2
@stijn 예, 아니오. 당신이 가리키는 Q & A는 그것이 참조하는 C ++ 표준의 버전을 명확히하지 않는 한 불완전합니다. 단순히 오도하는 일반적인 절대 / 항상 규칙을 널리 퍼뜨리는 것은 매우 쉽습니다. 독자가 David Abrahams 기사 및 참조에 익숙해 지거나 게시 날짜와 현재 C ++ 표준을 고려할 때까지 시간이 걸리지 않습니다. 따라서 게시 시점을 기준으로 내 답변과 내가 대답 한 답변이 모두 정확합니다.
mloskot

1
" 멀티 스레딩이 없는 한"아니오, MT는 결코 특별하지 않습니다.
curiousguy

3
나는 파티에 늦었지만 값으로 shared_ptr을 전달하려는 이유는 코드가 더 짧고 예쁘다는 것입니다. 진심으로. Value*짧고 읽을 수 있지만 나쁘기 때문에 이제 코드가 가득 차고 const shared_ptr<Value>&읽기 쉽고 덜 깔끔합니다. 무엇로 사용하는 것은 void Function(Value* v1, Value* v2, Value* v3)지금 void Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3), 사람들은이와 괜찮아?
Alex

7
@Alex 일반적인 관행은 클래스 바로 다음에 별칭 (typedef)을 만드는 것입니다. 예를 들면 class Value {...}; using ValuePtr = std::shared_ptr<Value>;다음과 같습니다. 그러면 함수가 더 단순 해 void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)집니다. 최대 성능을 얻습니다. 이것이 C ++을 사용하는 이유입니다. :)
4LegsDrivenCat

92

여기 허브 셔터의 포획

지침 : 소유권 공유 또는 전송과 같은 스마트 포인터 자체를 사용하거나 조작하지 않는 한 스마트 포인터를 함수 매개 변수로 전달하지 마십시오.

지침 : 함수가 값별 shared_ptr 매개 변수를 사용하여 힙 오브젝트의 소유권을 저장하고 공유 할 것이라고 표현하십시오.

지침 : 비 const shared_ptr & 매개 변수 만 사용하여 shared_ptr을 수정하십시오. 사본을 공유하고 소유권을 공유 할 것인지 확실하지 않은 경우에만 const shared_ptr &을 매개 변수로 사용하십시오. 그렇지 않으면 widget *을 대신 사용하십시오 (또는 null을 허용하지 않으면 widget &).


3
Sutter와 연결해 주셔서 감사합니다. 훌륭한 기사입니다. C ++ 14를 사용할 수있는 경우 선택적 <widget &>을 선호하여 widget *에 동의하지 않습니다. widget *이 이전 코드와 너무 모호합니다.
시조

3
widget * 및 widget &을 가능성으로 포함하면 +1입니다. 자세히 설명하면, 함수가 포인터 객체 자체를 검사 / 수정하지 않을 때 widget * 또는 widget &을 전달하는 것이 가장 좋은 옵션 일 것입니다. 인터페이스는 특정 포인터 유형을 필요로하지 않기 때문에보다 일반적이며, shared_ptr 참조 카운트의 성능 문제는 회피됩니다.
tgnottingham

4
나는 이것이 두 번째 지침 때문에 오늘 받아 들여질 대답이라고 생각합니다. 현재 허용되는 답변을 분명히 무효화합니다. 즉, 가치를 넘길 이유가 없습니다.
mbrt

62

개인적으로 나는 const참조를 사용할 것 입니다. 함수 호출을 위해 참조 카운트를 다시 감소시키기 위해 참조 카운트를 증가시킬 필요는 없습니다.


1
나는 당신의 대답을 다운 투표하지 않았지만, 이것이 선호의 문제이기 전에 고려해야 할 두 가지 가능성 각각에 장단점이 있습니다. 그리고 이러한 장단점을 알고 토론하는 것이 좋습니다. 그 후 모든 사람이 스스로 결정을 내릴 수 있습니다.
Danvil

@ Danvil : shared_ptr작동 방식 을 고려할 때 참조로 전달하지 않는 유일한 단점은 성능의 약간의 손실입니다. 여기에는 두 가지 원인이 있습니다. a) 포인터 앨리어싱 기능은 포인터를 의미하는 데이터와 카운터 (약한 참조의 경우 2)가 복사되므로 데이터 라운드를 복사하는 것이 약간 더 비쌉니다. b) 원자 기준 계수는 평범한 이전 증분 / 감소 코드보다 약간 느리지 만 스레드 안전을 위해 필요합니다. 그 외에도 두 가지 방법은 대부분의 의도와 목적에 동일합니다.
Evan Teran

37

에 의해 전달 const이 빠르다 참조. 당신이 그것을 저장 해야하는 경우, 예를 들어 일부 컨테이너에 ref. 복사 작업에 의해 카운트가 자동으로 증가합니다.


4
공감대는 숫자 없이도 의견을 뒷받침합니다.
kwesolowski

22

나는 한 번에, 아래의 코드를 실행 foo복용 shared_ptr에 의해 const&다시 함께 foo을 복용 shared_ptr값.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

인텔 코어 2 쿼드 (2.4GHz) 프로세서에서 x86 릴리스 빌드 VS2015 사용

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

값별 카피 버전은 10 배 느리게 진행되었습니다.
현재 스레드에서 동 기적으로 함수를 호출하는 경우 const&버전을 선호하십시오 .


1
어떤 컴파일러, 플랫폼 및 최적화 설정을 사용했는지 말할 수 있습니까?
Carlton

vs2015의 디버그 빌드를 사용하고 릴리스 빌드를 사용하도록 답변을 업데이트했습니다.
tcb


2
최적화는별로 도움이되지 않습니다. 문제는 복사본의 참조 횟수에 대한 잠금 경합입니다.
Alex

1
그것은 요점이 아니다. 이러한의 foo()이 객체를 사용하지 않는 있기 때문에 기능도 처음에 공유 포인터를 받아 안 : 그것은을 받아 들여야 int&하고 어떻게 p = ++x;호출 foo(*p);에서 main(). 함수는 무언가를 수행해야 할 때 스마트 포인터 객체를 받아들이며, 대부분 필요한 경우 함수 std::move()를 다른 곳으로 옮기는 것이므로 값별 매개 변수는 비용이 들지 않습니다.
eepp

15

C ++ 11부터는 생각보다 const & 보다 자주 을 사용해야합니다 .

기본 유형 T가 아닌 std :: shared_ptr을 사용하는 경우 무언가를 수행하려고하기 때문에 그렇게합니다.

당신이 원하는 경우 복사 할 곳,이 사본을 가지고 가고, 표준 : : 오히려 const를하여 복용 및 다음 나중에 복사하는 것보다, 내부 이동에 더 많은 의미가 있습니다. 함수를 호출 할 때 호출자에게 옵션 std :: move_shared_ptr을 차례로 설정하여 증가 및 감소 조작 세트를 저장하기 때문입니다. 아님 즉, 함수의 호출자는 함수를 호출 한 후 std :: shared_ptr이 필요한지 여부와 이동 여부에 따라 결정할 수 있습니다. const &로 전달하면 달성 할 수 없으므로 값으로 가져 오는 것이 좋습니다.

물론, 호출자가 둘 다 그의 shared_ptr을 더 오랫동안 필요로하고 (따라서 std :: move 그것을 할 수 없음) 함수에 일반 사본을 만들고 싶지 않다면 (약한 포인터를 원하거나 때로는 원할 때만) 조건에 따라 복사하려면 const &가 더 좋습니다.

예를 들어,

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

위에

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

이 경우 항상 내부적으로 사본을 작성하기 때문에


1

원자 단위 증가 및 감소가있는 shared_copy 복사 작업의 시간 비용을 알지 못하면 CPU 사용 문제가 훨씬 높아졌습니다. 나는 원자 적 증가를 기대하지 않았고 감소시키는 데 많은 비용이 소요될 수 있습니다.

내 테스트 결과에 따라 int32 원자 증가 및 감소는 비 원자 증가 및 감소보다 2 또는 40 배가 걸립니다. Windows 8.1의 3GHz Core i7에서 얻었습니다. 앞의 결과는 경합이 발생하지 않을 때 나옵니다. 후자는 경합 가능성이 높을 때 나타납니다. 원 자성 작업은 마지막 하드웨어 기반 잠금 상태입니다. 잠금은 잠금입니다. 경합이 발생하면 성능이 저하됩니다.

이것을 경험하면서, 나는 항상 byval (shared_ptr)보다 byref (const shared_ptr &)를 사용합니다.


1

최근 블로그 게시물이있었습니다 : https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

따라서 이에 대한 대답은 다음과 같습니다 const shared_ptr<T>&.
대신 기본 클래스를 전달하십시오.

기본적으로 합리적인 매개 변수 유형은 다음과 같습니다.

  • shared_ptr<T> -수정 및 소유권 취득
  • shared_ptr<const T> -수정하지 말고 소유권을 가지십시오
  • T& -수정, 소유권 없음
  • const T& -수정하지 않고 소유권 없음
  • T -수정, 소유권 없음, 저렴한 복사

@accel이 https://stackoverflow.com/a/26197326/1930508 에서 지적했듯이 Herb Sutter의 조언은 다음과 같습니다.

사본을 공유하고 소유권을 공유 할 것인지 확실하지 않은 경우에만 const shared_ptr &을 매개 변수로 사용하십시오.

그러나 얼마나 많은 경우 확실하지 않습니까? 그래서 이것은 드문 상황입니다


0

shared_ptr을 값으로 전달하면 비용이 발생하며 가능한 경우 피해야한다는 것은 알려진 문제입니다.

shared_ptr을 지나가는 비용

대부분의 경우 참조로 shared_ptr을 전달하고 const 참조로 더 잘 전달합니다.

cpp 핵심 지침에는 shared_ptr을 전달하기위한 특정 규칙이 있습니다.

R.34 : shared_ptr 매개 변수를 사용하여 함수가 부분 소유자임을 표시

void share(shared_ptr<widget>);            // share -- "will" retain refcount

값으로 shared_ptr을 전달하는 경우의 예는 호출자가 공유 오브젝트를 비동기 수신자에게 전달하는 경우입니다. 즉 호출자가 작업을 완료하기 전에 호출자가 범위를 벗어납니다. 수신자는 값으로 share_ptr을 사용하여 공유 객체의 수명을 "연장"해야합니다. 이 경우 shared_ptr에 대한 참조를 전달하는 것은 수행되지 않습니다.

공유 객체를 작업 스레드에 전달하는 것도 마찬가지입니다.


-4

shared_ptr은 충분히 크지 않으며, 생성자 / 소멸자는 복사본에서 오버 헤드가 충분 해 참조에 의한 전달과 복사본의 전달 성능을 고려하기에 충분한 작업을 수행하지 않습니다.


15
당신은 그것을 측정 했습니까?
curiousguy

2
@stonemetal : 새로운 shared_ptr을 생성하는 동안 원자 명령어는 어떻습니까?
Quarra

이것은 비 POD 유형이므로 대부분의 ABI에서 "값으로"전달해도 실제로 포인터가 전달됩니다. 실제로 문제가되는 것은 바이트의 실제 복사가 아닙니다. asm 출력에서 ​​볼 수 있듯이 shared_ptr<int>by by 값을 전달하면 100 x86 개 이상의 명령이 필요합니다 ( lock참조 카운트를 원자 적으로 증가 / 감소시키는 값 비싼 ed 명령 포함 ). 상수 참조로 전달하는 것은 무엇이든 포인터를 전달하는 것과 같습니다 (이 예제에서 Godbolt 컴파일러 탐색기의 꼬리 호출 최적화는 이것을 호출 대신 간단한 jmp로 만듭니다 : godbolt.org/g/TazMBU ).
Peter Cordes

TL : DR : 이것은 복사 생성자가 바이트 복사보다 더 많은 작업을 수행 할 수있는 C ++입니다. 이 답변은 총 쓰레기입니다.
Peter Cordes

2
stackoverflow.com/questions/3628081/shared-ptr-horrible-speed 예를 들어 값으로 전달 된 공유 포인터와 참조로 전달되는 포인터의 런타임 차이는 약 33 %입니다. 성능이 중요한 코드를 작업하는 경우 기본 포인터를 사용하면 성능이 크게 향상됩니다. 따라서 기억하는 경우 const ref로 전달하십시오. 그렇지 않으면 크게 중요하지 않습니다. shared_ptr이 필요하지 않으면 사용하지 않는 것이 훨씬 중요합니다.
stonemetal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.