c ++-std :: shared_ptr 또는 boost :: shared_ptr에 대한 참조 전달


115

를 사용하여 작업해야하는 함수가있는 경우 shared_ptr참조를 전달하는 것이 더 효율적이지 shared_ptr않습니까 (객체 복사를 방지하기 위해 )? 가능한 나쁜 부작용은 무엇입니까? 두 가지 가능한 경우를 생각합니다.

1) 함수 내부에서 다음과 같이 인수로 복사본이 만들어집니다.

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) 함수 내에서 인수는 다음과 같이 사용됩니다.

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

두 경우 모두 boost::shared_ptr<foo>참조 대신 값 을 전달해야하는 좋은 이유를 알 수 없습니다 . 값으로 전달하면 복사로 인해 참조 카운트가 "일시적으로"증가한 다음 함수 범위를 종료 할 때 감소합니다. 내가 뭔가를 간과하고 있습니까?

명확히하기 위해 몇 가지 답변을 읽은 후 조기 최적화 문제에 완벽하게 동의하며 항상 핫스팟에서 먼저 프로파일 링 한 다음 작업하려고합니다. 내 질문은 내가 의미하는 바를 알고 있다면 순전히 기술적 인 코드 관점에서 더 많이 나왔습니다.


질문의 태그를 수정할 수 있는지 모르겠지만 거기에 부스트 태그를 추가해보세요. 이 질문을 찾아 보았지만 부스트 및 스마트 포인터 태그를 찾았 기 때문에 찾을 수 없었습니다. 그래서 내 질문을 작성한 직후에 질문을 찾았습니다
Edison Gustavo Muenz

답변:


113

고유 한 shared_ptr인스턴스 의 요점은 이것이 shared_ptr범위 내에있는 한 참조 횟수가 1 이상이기 때문에 가리키는 개체가 계속 존재 하도록 (가능한 한) 보장 하는 것입니다.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

따라서에 대한 참조를 사용하여 shared_ptr해당 보장을 비활성화합니다. 따라서 두 번째 경우 :

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

sp->do_something()널 포인터로 인해 폭발 하지 않을 것이라는 것을 어떻게 알 수 있습니까?

그것은 모두 코드의 '...'섹션에 무엇이 있는지에 달려 있습니다. shared_ptr동일한 객체에 대한 a 를 지우는 부작용 (코드의 다른 부분)이있는 첫 번째 '...'중에 무언가를 호출하면 어떻게 됩니까? 그리고 그것이 shared_ptr그 물체에 남아있는 유일한 유일한 것이면 어떻게 될까요? Bye bye object, 당신이 시도하고 사용하려는 곳.

따라서이 질문에 답하는 방법에는 두 가지가 있습니다.

  1. 함수 본체에서 개체가 죽지 않을 것이라는 확신이들 때까지 전체 프로그램의 소스를 매우주의 깊게 조사하십시오.

  2. 매개 변수를 다시 참조 대신 고유 한 개체로 변경합니다.

여기에 적용되는 일반적인 조언 : 프로파일 러에서 현실적인 상황에서 제품의 시간을 정하고 변경하려는 변경 사항이 성능에 큰 차이가 있습니다.

댓글 작성자 JQ 업데이트

여기에 인위적인 예가 있습니다. 의도적으로 간단하므로 실수는 분명합니다. 실제 예에서 실수는 실제 세부 사항의 레이어에 숨겨져 있기 때문에 그다지 분명하지 않습니다.

어딘가에 메시지를 보내는 기능이 있습니다. 큰 메시지 일 수 있으므로 std::string여러 장소로 전달 될 때 복사 될 가능성이 있는 a 를 사용하는 대신 a shared_ptrto 문자열을 사용 합니다.

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(이 예제에서는 콘솔로 "보내기"만하면됩니다).

이제 이전 메시지를 기억하는 기능을 추가하려고합니다. 우리는 다음과 같은 동작을 원합니다. 가장 최근에 보낸 메시지를 포함하는 변수가 존재해야하지만 현재 메시지가 전송되는 동안에는 이전 메시지가 없어야합니다 (변수는 보내기 전에 재설정되어야 함). 따라서 새 변수를 선언합니다.

std::shared_ptr<std::string> previous_message;

그런 다음 지정한 규칙에 따라 기능을 수정합니다.

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

따라서 전송을 시작하기 전에 현재 이전 메시지를 삭제하고 전송이 완료된 후 새 이전 메시지를 저장할 수 있습니다. 문제 없다. 다음은 몇 가지 테스트 코드입니다.

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

예상대로 이것은 Hi!두 번 인쇄 됩니다.

이제 코드를보고 생각하는 메인테이너 씨가 따라옵니다.이 매개 변수 send_message는 다음과 shared_ptr같습니다.

void send_message(std::shared_ptr<std::string> msg)

분명히 다음과 같이 변경할 수 있습니다.

void send_message(const std::shared_ptr<std::string> &msg)

이것이 가져올 성능 향상을 생각하십시오! (일부 채널을 통해 일반적으로 큰 메시지를 보내려고하므로 성능 향상이 측정 할 수 없을 정도로 작습니다.)

그러나 실제 문제는 이제 테스트 코드가 정의되지 않은 동작을 표시한다는 것입니다 (Visual C ++ 2010 디버그 빌드에서는 충돌이 발생 함).

유지 관리자는 이에 놀랐지 만 send_message문제 발생을 막기 위해 방어 검사를 추가합니다 .

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

그러나 물론 호출 msg될 때 null이 아니기 때문에 여전히 진행되고 충돌 send_message합니다.

내가 말했듯이 모든 코드가 사소한 예에서 너무 가깝기 때문에 실수를 쉽게 찾을 수 있습니다. 그러나 서로에 대한 포인터를 개최 가변 객체 사이의 더 복잡한 관계와 실제 프로그램에서, 쉽게 만드는 실수를 감지하는 데 필요한 테스트 케이스를 구성하는 실수를, 하드.

함수가 shared_ptr계속해서 null이 아닌 것에 의존 할 수 있기를 원하는 쉬운 솔루션 은 함수가 shared_ptr기존에 대한 참조에 의존하는 대신 자체 true를 할당 하는 것 shared_ptr입니다.

단점은 복사 된 a shared_ptr가 자유롭지 않다는 것입니다. "잠금없는"구현도 스레딩 보장을 준수하기 위해 연동 된 작업을 사용해야합니다. 따라서 a shared_ptr를 .txt 파일로 변경하여 프로그램 속도를 크게 높일 수있는 상황이있을 수 있습니다 shared_ptr &. 그러나 이것은 모든 프로그램에 안전하게 적용 할 수있는 변경 사항은 아닙니다. 프로그램의 논리적 의미를 변경합니다.

std::string대신 std::shared_ptr<std::string>, 대신 전체를 사용하면 유사한 버그가 발생합니다 .

previous_message = 0;

메시지를 정리하기 위해 다음과 같이 말했습니다.

previous_message.clear();

그런 다음 증상은 정의되지 않은 동작 대신 실수로 빈 메시지를 보내는 것입니다. 매우 큰 문자열의 추가 복사본 비용은를 복사하는 비용보다 훨씬 클 shared_ptr수 있으므로 절충안이 다를 수 있습니다.


16
전달 된 shared_ptr은 이미 호출 사이트의 범위에 있습니다. 매달려있는 포인터로 인해이 질문의 코드가 폭발하는 정교한 시나리오를 만들 수 있지만 참조 매개 변수보다 더 큰 문제가 있다고 가정합니다!
Magnus Hoff

10
회원에 저장 될 수 있습니다. 그 구성원을 지우는 일을 할 수 있습니다. smart_ptr의 요점은 호출 스택 주위에 중첩되는 계층 또는 범위에서 수명을 조정하지 않아도되는 것이므로 수명이 그러한 프로그램에서 그렇게하지 않는다고 가정하는 것이 가장 좋습니다.
Daniel Earwicker

7
그래도 내 관점이 아닙니다! 내가 말하는 것이 내 코드와 관련이 있다고 생각한다면 나를 이해하지 못했을 수도 있습니다. 저는 shared_ptr이 애초에 존재하는 이유에 대한 피할 수없는 의미에 대해 이야기하고 있습니다. 많은 객체 수명은 단순히 함수 호출과 관련이 없습니다.
Daniel Earwicker

8
@DanielEarwicker는 귀하의 모든 요점에 완전히 동의하며 반대 수준에 놀랐습니다. 당신의 관심사를 더욱 의미있게 만드는 것은 스레딩인데, 이것이 관련 될 때 객체 유효성에 대한 보장이 훨씬 더 중요해집니다. 좋은 대답입니다.
radman 2013 년

3
얼마 전 공유 포인터에 대한 참조를 전달하기 때문에 발생한 매우 심각한 버그를 추적했습니다. 코드는 객체의 상태 변경을 처리하고 있었고 객체의 상태가 변경된 것을 알았을 때 이전 상태의 객체 컬렉션에서 제거하고 새 상태의 객체 컬렉션으로 이동했습니다. 제거 작업은 객체에 대한 마지막 공유 포인터를 파괴했습니다. 컬렉션의 공유 포인터에 대한 참조에서 멤버 함수가 호출되었습니다. 팔. Daniel Earwicker가 옳습니다.
David Schwartz

115

나는 가장 많이 득표 한 답변에 동의하지 않는다는 것을 알았 기 때문에 전문가 의견을 찾았고 여기에 있습니다. 에서 http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter : "shared_ptrs를 통과하면 복사본이 비쌉니다."

Scott Meyers : "값으로 전달하든 참조로 전달하든 shared_ptr에 특별한 것은 없습니다. 다른 사용자 정의 유형에 사용하는 것과 똑같은 분석을 사용하십시오. 사람들은 shared_ptr이 어떻게 든 해결하는 이러한 인식을 가지고있는 것 같습니다. 모든 관리 문제, 그리고 그것이 작기 때문에 가치로 전달하는 것이 필연적으로 저렴합니다. 복사해야하고 그와 관련된 비용이 있습니다 ... 가치로 전달하는 것이 비싸기 때문에 벗어날 수 있다면 내 프로그램에서 적절한 의미를 가지고 있으면 대신 const 또는 reference에 대한 참조로 전달할 것입니다. "

Herb Sutter : "항상 const에 대한 참조로 전달합니다. 아주 가끔 호출 한 내용이 참조를 가져온 것을 수정할 수 있다는 것을 알고 있기 때문에 값으로 전달할 수 있습니다. 매개 변수로 복사하면 오 어차피 살아 있기 때문에 참조 횟수를 늘릴 필요가 거의 없습니다. 참조로 전달해야하므로 그렇게 해주세요. "

업데이트 : Herb는 여기에서 확장되었습니다 : http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ 이야기의 교훈은 통과해서는 안된다는 것입니다. shared_ptrs 전혀 "소유권을 공유하거나 이전하는 것과 같이 스마트 포인터 자체를 사용하거나 조작하려는 경우는 제외됩니다."


8
좋은 발견! 이 주제에 대한 두 명의 최고의 전문가가 SO에 대한 관습적인 통념을 공개적으로 반박하는 것을 보는 것이 좋습니다.
Stephan Tolksdorf

3
"값으로 전달하든 참조로 전달하든 shared_ptr에 특별한 것은 없습니다."-나는 이것에 정말로 동의하지 않습니다. 특별합니다. 개인적으로 나는 그것을 안전하게 플레이하고 약간의 성능 타격을 받고 싶습니다. 최적화해야 할 특정 코드 영역이 있다면 const ref에 의한 shared_ptr 전달의 성능 이점을 살펴볼 것입니다.
JasonZ

3
shared_ptrs의 남용에 대한 합의가 있었지만 값에 의한 전달 대 참조 문제에 대한 합의가 없다는 점도 흥미 롭습니다 .
Nicol Bolas 2012

2
Scott Meyers : "내 프로그램에서 적절한 의미론으로 벗어날 수 있다면 ..."즉, 매개 변수를 변경하는 const &것이 의미론에 영향을 미칠지 여부를 파악하는 것이 매우 쉽다는 점을 지적하는 내 대답과 전혀 모순되지 않습니다. 간단한 프로그램.
Daniel Earwicker

7
Herb Sutter : "아주 가끔은 호출 한 내용을 알고 있기 때문에 참조를받은 내용이 수정 될 수 있습니다." 다시 말하지만, 사소한 경우에 대한 약간의 면제이므로 내 대답과 모순되지 않습니다. 질문은 남아 있습니다. const ref를 사용하는 것이 안전한지 어떻게 수 있습니까? 단순한 프로그램에서 증명하기가 매우 쉽지만 복잡한 프로그램에서는 그렇게 쉽지 않습니다. 하지만, 이봐, 이것은 이다 권리, C ++는, 우리는 이상의 거의 모든 다른 엔지니어링 문제를 조기 마이크로 최적화를 선호 있도록! :)
Daniel Earwicker

22

나는 당신과 당신이 함께 일하는 다른 프로그래머가 당신 이 무엇을하고 있는지 정말로 알지 못한다면이 관행에 반대 할 것을 권합니다 .

첫째, 클래스에 대한 인터페이스가 어떻게 진화할지 모르고 다른 프로그래머가 나쁜 일을하는 것을 방지하려고합니다. 참조로 shared_ptr을 전달하는 것은 프로그래머가보기를 기대하는 것이 아닙니다. 왜냐하면 관용적이지 않고 잘못 사용하기 쉽기 때문입니다. 방어 적으로 프로그래밍 : 인터페이스를 잘못 사용하기 어렵게 만듭니다. 참조로 전달하면 나중에 문제가 발생할 수 있습니다.

둘째,이 특정 클래스가 문제가 될 것이라는 것을 알 때까지 최적화하지 마십시오. 먼저 프로필을 작성하고 프로그램이 참조를 통해 전달되는 부스트를 정말로 필요로한다면 아마도 가능할 것입니다. 그렇지 않으면 작은 것 (즉, 값으로 전달하는 데 필요한 추가 N 명령)에 땀을 흘리지 말고 대신 디자인, 데이터 구조, 알고리즘 및 장기 유지 관리 가능성에 대해 걱정하십시오.


litb의 대답은 기술적으로 정확하지만 프로그래머의 "게으름"을 과소 평가하지 마십시오 (저도 게으르다!). littlenag의 대답은 더 낫습니다. shared_ptr에 대한 참조는 예상치 못한 일이 될 것이고, 아마도 (아마도) 향후 유지 관리를 더 어렵게 만드는 불필요한 최적화 일 것입니다.
netjeff

18

예, 거기에서 참조하는 것이 좋습니다. 메소드에 공유 소유권을 부여 할 의도가 없습니다. 그것은 단지 그것과 함께 일하기를 원합니다. 어쨌든 복사하기 때문에 첫 번째 경우에 대한 참조도 가져올 수 있습니다. 그러나 첫 번째 경우 에는 소유권이 필요합니다. 한 번만 복사하는 트릭이 있습니다.

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

반환 할 때도 복사해야합니다 (예 : 참조를 반환하지 않음). 클래스가 클라이언트가 무엇을하는지 모르기 때문입니다 (포인터를 저장할 수 있고 빅뱅이 발생할 수 있습니다). 나중에 병목 현상이 발생하면 (첫 번째 프로필!) 참조를 반환 할 수 있습니다.


편집 : 물론 다른 사람들이 지적했듯이 이것은 코드를 알고 전달 된 공유 포인터를 어떤 식 으로든 재설정하지 않는다는 것을 알고있는 경우에만 해당됩니다. 의심스러운 경우 값으로 전달하십시오.


11

shared_ptrs 를 전달하는 것이 현명합니다 const&. 이것은 문제를 일으키지 않을 것입니다 ( shared_ptrEarwicker가 자세히 설명했듯이 함수 호출 중에 참조 된 항목 이 삭제되는 드문 경우 제외 ). 이러한 항목을 많이 전달하면 더 빠를 수 있습니다. 생각해 내다; 기본값 boost::shared_ptr은 스레드 안전이므로 복사에는 스레드 안전 증분이 포함됩니다.

임시 객체는 상수가 아닌 참조로 전달되지 않을 수 있으므로을 사용하는 const&것이 아니라 사용하십시오 &. (MSVC의 언어 확장을 사용하면 어쨌든 할 수 있습니다)


3
예, 저는 항상 const 참조를 사용합니다. 제 예제에 넣는 것을 잊었습니다. 어쨌든 MSVC는 상수가 아닌 참조를 버그가 아닌 임시에 바인딩 할 수 있지만 기본적으로 "C / C ++-> 언어-> 언어 확장 비활성화"속성이 "NO"로 설정되어 있기 때문입니다. 활성화하면 컴파일되지 않습니다 ...
abigagli

abigagli : 진심으로? 단! 나는 직장에서 이것을 시행 할 것입니다. 내일 먼저;)
Magnus Hoff

10

두 번째 경우에는 이렇게하는 것이 더 간단합니다.

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

당신은 그것을 다음과 같이 부를 수 있습니다.

only_work_with_sp(*sp);

3
포인터를 복사 할 필요가 없을 때 객체 참조를 사용하는 규칙을 채택하면 의도를 문서화하는 역할도합니다. 또한 const 참조를 사용할 수있는 기회를 제공합니다.
Mark Ransom

예, 호출 된 함수가 해당 객체에 대해 "기억"하지 않는다는 것을 표현하기위한 수단으로 객체에 대한 참조 사용에 동의합니다. 일반적으로 함수가 객체의 "추적을 유지"하는 경우 포인터 형식 인수를 사용합니다
abigagli

3

함수가 명시 적으로 포인터를 수정하지 않는 한 "일반"참조를 피할 것입니다.

A const &는 작은 함수를 호출 할 때 현명한 마이크로 최적화 일 수 있습니다. 예를 들어 일부 조건을 인라인하는 것과 같은 추가 최적화를 가능하게합니다. 또한 증가 / 감소는 스레드로부터 안전하므로 동기화 지점입니다. 그러나 이것이 대부분의 시나리오에서 큰 차이를 만들 것이라고 기대하지는 않습니다.

일반적으로 그럴 이유가없는 한 더 간단한 스타일을 사용해야합니다. 그런 다음 const &일관되게 사용 하거나 몇 군데에서만 사용하는 이유에 대한 설명을 추가하십시오.


3

포인터와 함께 전달되는 함수가 포인터를 소유하지 않는다는 의미 인 const 참조로 공유 포인터를 전달하는 것을 옹호합니다. 이는 개발자를위한 깨끗한 관용구입니다.

유일한 함정은 여러 스레드 프로그램에서 공유 포인터가 가리키는 객체가 다른 스레드에서 파괴된다는 것입니다. 따라서 단일 스레드 프로그램에서 공유 포인터의 const 참조를 사용하는 것이 안전하다고 말하는 것이 안전합니다.

비 상수 참조로 공유 포인터를 전달하는 것은 때때로 위험합니다. 그 이유는 함수가 반환 된 후에도 여전히 유효한 것으로 간주되는 객체를 파괴하기 위해 함수가 내부에서 호출 할 수있는 스왑 및 재설정 함수 때문입니다.

그것은 조기 최적화에 관한 것이 아니라, 여러분이하고 싶은 일이 분명하고 동료 개발자들이 코딩 관용구를 확고히 채택했을 때 불필요한 CPU 사이클 낭비를 피하는 것입니다.

내 2 센트 :-)


1
위의 David Schwartz의 주석을 참조하십시오. "... 공유 포인터에 대한 참조를 전달하기 때문에 발생한 매우 심각한 버그를 추적했습니다. 코드는 객체의 상태 변경을 처리하고 있었고 객체의 상태가 변경되었음을 발견했을 때, 이전 상태의 객체 컬렉션에서 제거하고 새 상태의 객체 컬렉션으로 이동했습니다. 제거 작업은 객체에 대한 마지막 공유 포인터를 파괴했습니다. 멤버 함수는 공유 포인터에 대한 참조에서 호출되었습니다. 컬렉션의 붐 .... "
제이슨 해리슨

3

여기의 모든 장단점은 실제로 shared_ptr뿐만 아니라 참조로 전달되는 모든 유형으로 일반화 될 수 있습니다. 제 생각에는 참조, const 참조 및 값으로 전달하는 의미를 알고 올바르게 사용해야합니다. 그러나 모든 참조가 나쁘다고 생각하지 않는 한 shared_ptr을 참조로 전달하는 것은 본질적으로 잘못된 것이 아닙니다.

예제로 돌아가려면 :

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

sp.do_something()매달려있는 포인터로 인해 폭발 하지 않을 것이라는 것을 어떻게 알 수 있습니까?

진실은 shared_ptr이든 sp아니든, const이든 아니든, 직접 또는 간접적으로 스레드 간의 소유권을 공유하고 , 객체를 사용하지 않고 delete this, 순환 소유권 또는 기타 소유권 오류가있는 것과 같은 디자인 결함이있는 경우 발생할 수 있습니다.


2

내가 아직 언급하지 않은 한 가지는 참조로 공유 포인터를 전달할 때 기본 클래스 공유 포인터에 대한 참조를 통해 파생 클래스 공유 포인터를 전달하려는 경우 얻을 수있는 암시 적 변환이 손실된다는 것입니다.

예를 들어,이 코드는 오류를 생성하지만 test()공유 포인터가 참조로 전달되지 않도록 변경하면 작동합니다 .

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}

1

나는 당신이 조기 최적화에 익숙하고 학문적 목적으로 또는 성능이 저조한 기존 코드를 분리했기 때문에 이것을 요구 한다고 가정합니다 .

참고로 통과해도 괜찮습니다

const 참조로 전달하는 것이 더 좋으며 가리키는 객체에 대해 const-ness를 강요하지 않기 때문에 일반적으로 사용할 수 있습니다.

참조 사용으로 인해 포인터를 잃을 위험 이 없습니다 . 이 참조는 스택의 초기에 스마트 포인터의 복사본이 있고 하나의 스레드 만 호출 스택을 소유하므로 기존 복사본이 사라지지 않는다는 증거입니다.

참조를 사용하는 것은 종종 더 효율적 당신이 언급 이유로, 하지만 보장 . 객체를 역 참조하는 것도 작업이 필요할 수 있음을 기억하십시오. 이상적인 참조 사용 시나리오는 코딩 스타일에 많은 작은 함수가 포함되어있는 경우 포인터가 사용되기 전에 함수에서 함수로 전달됩니다.

항상 스마트 포인터를 참조로 저장하지 않아야합니다 . 귀하의 Class::take_copy_of_sp(&sp)예는 올바른 사용법을 보여줍니다.


1
"참조 사용으로 인해 포인터를 잃을 위험이 없습니다. 그 참조는 스택의 초기에 스마트 포인터의 복사본이 있다는 증거입니다."아니면 데이터 멤버 ...?
Daniel Earwicker

boost :: thread 및 boost :: ref : boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = 새로운 boost :: thread (functionPointer); ... 삭제 sharedPtrInstance
제이슨 해리슨

1

const 정확성에 관심이 없다고 가정하면 (또는 그 이상은 함수가 전달되는 데이터의 소유권을 수정하거나 공유 할 수 있도록 허용하는 것을 의미합니다) boost :: shared_ptr을 값으로 전달하는 것이 참조로 전달하는 것보다 안전합니다. 원래의 boost :: shared_ptr이 자신의 수명을 제어하도록 허용합니다. 다음 코드의 결과를 고려하십시오.

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

위의 예에서 sharedA 를 참조로 전달 하면 FooTakesReference 가 원래 포인터를 재설정하여 사용 횟수를 0으로 줄여 데이터를 파괴한다는 것을 알 수 있습니다. 그러나 FooTakesValue 는 원래 포인터를 재설정 할 수 없으므로 sharedB의 데이터를 계속 사용할 수 있습니다. 다른 개발자가 필연적으로 와서 sharedA의 취약한 존재 에 편승하려고 시도하면 혼란이 뒤 따릅니다 . 그러나 운이 좋은 sharedB 개발자는 자신의 세계에서 모든 것이 옳기 때문에 일찍 집으로 돌아갑니다.

이 경우 코드 안전성은 복사로 인한 속도 향상보다 훨씬 큽니다. 동시에 boost :: shared_ptr은 코드 안전성을 향상시키기위한 것입니다. 이런 종류의 틈새 최적화가 필요한 경우 사본에서 참조로 이동하는 것이 훨씬 쉽습니다.


1

Sandy는 다음과 같이 썼습니다. "여기에있는 모든 장단점은 shared_ptr뿐만 아니라 참조로 전달 된 모든 유형으로 실제로 일반화 될 수있는 것 같습니다."

어느 정도 사실이지만 shared_ptr 사용의 요점은 객체 수명과 관련된 문제를 제거하고 컴파일러가이를 처리하도록하는 것입니다. 참조로 공유 포인터를 전달하고 참조 카운트 개체의 클라이언트가 개체 데이터를 해제 할 수있는 상수가 아닌 메서드를 호출하도록 허용하려는 경우 공유 포인터를 사용하는 것은 거의 의미가 없습니다.

성능이 문제가 될 수 있고 드물게 정당화 될 수 있기 때문에 이전 문장에서 "거의"라고 썼습니다.하지만이 시나리오를 직접 피하고 심각하게 살펴 보는 것과 같은 가능한 다른 모든 최적화 솔루션을 직접 찾습니다. 또 다른 수준의 간접적, 게으른 평가 등을 추가 할 때

작성자의 과거에 존재하거나 작성자의 메모리를 게시하는 코드는 동작, 특히 객체 수명에 대한 동작에 대한 암시 적 가정이 필요하고 명확하고 간결하며 읽기 쉬운 문서가 필요하며 많은 클라이언트가 어쨌든 그것을 읽지 않을 것입니다! 단순함이 거의 항상 효율성을 능가하며 거의 항상 다른 방법으로 효율적입니다. 참조 횟수 개체 (및 같음 연산자)의 복사 생성자에 의한 딥 복사를 피하기 위해 참조로 값을 전달해야하는 경우 딥 복사 된 데이터를 참조 횟수 포인터로 만드는 방법을 고려해야합니다. 빨리 복사했습니다. (물론 귀하의 상황에 적용되지 않을 수있는 하나의 설계 시나리오 일뿐입니다.)


1

저는 스마트 포인터를 가치로 전달하는 것에 대한 원칙이 매우 강한 프로젝트에서 일했습니다. 성능 분석을 요청 받았을 때 스마트 포인터의 참조 카운터 증가 및 감소를 위해 응용 프로그램이 사용 된 프로세서 시간의 4 ~ 6 %를 소비한다는 사실을 발견했습니다.

Daniel Earwicker가 설명한대로 이상한 경우에 문제가 발생하지 않도록 스마트 포인터를 값별로 전달하려면 지불 한 가격을 이해해야합니다.

참조를 사용하기로 결정한 경우 const 참조를 사용하는 주된 이유는 인터페이스에서 사용하는 클래스를 상속하는 클래스에서 객체에 대한 공유 포인터를 전달해야 할 때 암시 적 업 캐스팅을 가능하게하는 것입니다.


0

litb가 말한 것 외에도 두 번째 예제에서 const 참조를 전달하는 것이 아마도 실수로 수정하지 않도록 할 수 있다는 점을 지적하고 싶습니다.


0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. 값으로 전달 :

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. 참조로 전달 :

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. 포인터로 전달 :

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }

0

모든 코드에는 어떤 의미가 있어야합니다. 응용 프로그램의 모든 곳 에서 공유 포인터를 값으로 전달하면 "다른 곳에서 무슨 일이 벌어지고 있는지 확실하지 않으므로 원시 안전을 선호합니다 "를 의미합니다. 이것은 코드를 참조 할 수있는 다른 프로그래머들에게 좋은 신뢰 신호라고 부르는 것이 아닙니다.

어쨌든 함수가 const 참조를 받고 "확실하지 않은"경우에도 함수의 헤드에 공유 포인터의 복사본을 만들어 포인터에 대한 강력한 참조를 추가 할 수 있습니다. 이것은 또한 디자인에 대한 힌트로 볼 수 있습니다 ( "포인터는 다른 곳에서 수정할 수 있습니다").

예, IMO, 기본값은 " pass by const reference "입니다.

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