문제

다음과 같이 작동해야 하는 함수가 있는 경우 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> 참조 대신 값으로.값을 전달하면 복사로 인해 참조 횟수가 "일시적으로"만 증가하고 함수 범위를 종료할 때 감소합니다.내가 뭔가를 간과하고 있는 걸까?

몇 가지 답변을 읽은 후 명확히 하자면 다음과 같습니다.저는 조기 최적화 문제에 전적으로 동의하며, 항상 먼저 프로파일링한 다음 핫스팟에서 작업하려고 노력합니다.내 질문은 내가 의미하는 바를 알고 계시다면 순전히 기술적인 코드 관점에서 나온 것입니다.

도움이 되었습니까?

해결책

구별되는 점 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
}

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

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

어떻게 알았어 sp->do_something() 널 포인터로 인해 폭발하지 않습니까?

그것은 모두 코드의 '...' 섹션에 무엇이 있는지에 따라 다릅니다.첫 번째 '...' 중에 무언가를 호출하면(코드의 다른 부분 어딘가에) shared_ptr 같은 물건에?그리고 그것이 유일하게 남아있는 별개의 것이라면 어떻게 될까요? shared_ptr 그 물건에?안녕 개체, 이제 막 사용하려고 하는 곳입니다.

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

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

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

여기에 적용되는 일반적인 조언:프로파일러에서 현실적인 상황에서 제품 시간을 측정하고 원하는 변경이 성능에 상당한 차이를 가져올 것이라고 결정적으로 측정할 때까지 성능을 위해 위험하게 코드를 변경하지 마십시오.

댓글 작성자 JQ 업데이트

여기 인위적인 예가 있습니다.의도적으로 단순하기 때문에 실수가 명백해질 것입니다.실제 사례에서는 실수가 실제 세부 사항의 레이어에 숨겨져 있기 때문에 그렇게 명확하지 않습니다.

어딘가에 메시지를 보내는 기능이 있습니다.큰 메시지가 될 수 있으므로 std::string 여러 장소로 전달되면서 복사될 가능성이 높으므로 우리는 shared_ptr 문자열로:

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! 두 배.

이제 코드를 보고 다음과 같이 생각하는 Mr Maintenanceer가 등장합니다.아, 그 매개변수는 send_messageshared_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이 아닌 것은 함수가 자신의 true를 할당하기 위한 것입니다. shared_ptr, 기존 참조에 의존하기보다는 shared_ptr.

단점은 shared_ptr 무료가 아닙니다:"잠금 없는" 구현이라도 스레딩 보장을 존중하기 위해 연동 작업을 사용해야 합니다.따라서 프로그램을 변경하여 프로그램 속도를 크게 높일 수 있는 상황이 있을 수 있습니다. shared_ptr 으로 shared_ptr &.그러나 이는 모든 프로그램에 안전하게 적용할 수 있는 변경 사항은 아닙니다.프로그램의 논리적 의미를 변경합니다.

다음을 사용하면 비슷한 버그가 발생합니다. std::string 대신에 전체적으로 std::shared_ptr<std::string>, 그리고 대신:

previous_message = 0;

메시지를 지우기 위해 우리는 다음과 같이 말했습니다:

previous_message.clear();

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

다른 팁

나는 가장 높은 투명한 대답에 동의하지 않는 것을 발견했다. 그래서 나는 전문적인 의견을 찾고 있었고 여기에있다. 에서 http://channel9.msdn.com/shows/gydepep/c-and-beyond-2011-scott-drei-and-herb-us-us-ans- anything

Herb Sutter : "shared_ptr 's를 통과하면 사본이 비싸다"

Scott Meyers : "Shared_PTR에 대해 특별한 것은 없습니다. 가치별로 전달하거나 참조로 전달하는지 여부에 따라 다른 사용자 정의 유형에 사용하는 동일한 분석을 사용하십시오. 사람들은 Shared_PTR이 어떻게 든 해결되는이 인식을 가지고있는 것 같습니다. 모든 관리 문제, 그리고 작기 때문에 가치별로 통과하는 것이 저렴합니다. 복사해야하며 그와 관련된 비용이 있습니다 ... 가치별로 통과하는 데 비용이 많이 들기 때문에 도망 갈 수 있다면 도망 갈 수 있습니다. 내 프로그램에 적절한 의미론이있는 것은 Const 또는 Reference 대신 참조로 전달할 것입니다. "

Herb Sutter : "항상 Const를 참조하여 전달하십시오. 매우 때때로 당신이 전화 한 내용이 당신이 참조 한 것을 수정할 수 있다는 것을 알고 있기 때문에 아마도 가치로 통과 할 수 있습니다 ... 매개 변수로 복사하면 OH. 내 선하심은 어쨌든 그 참조 수가 살아 있기 때문에 그 참조 수를 충돌시킬 필요가 거의 없으며, 참조로 전달해야하므로 그렇게하십시오. "

업데이트 : Herb가 여기에서 확장되었습니다. http://herbsterter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/, 이야기의 도덕은 "소유권을 공유하거나 양도하는 것과 같은 스마트 포인터 자체를 사용하거나 조작하지 않는 한"Shared_ptr을 전혀 통과해서는 안된다는 것입니다.

당신과 당신이 실제로 함께 일하는 다른 프로그래머가 아니라면이 관행에 대해 조언합니다. 진짜 당신이 무엇을하고 있는지 알고 있습니다.

첫째, 수업의 인터페이스가 어떻게 발전 할 수 있는지 전혀 모르고 다른 프로그래머가 나쁜 일을하는 것을 막기를 원합니다. Shared_ptr을 참조별로 전달하는 것은 프로그래머가 관용적이지 않기 때문에 볼 수있는 것이 아니며 잘못 사용하기 쉽습니다. 방어 적으로 프로그램 : 인터페이스를 잘못 사용하기 어렵게 만듭니다. 참조로 통과하는 것은 나중에 문제를 초대 할 것입니다.

둘째,이 특정 클래스가 문제가 될 것임을 알 때까지 최적화하지 마십시오. 먼저 프로파일을 한 다음 프로그램에 참조로 통과하여 부스트가 실제로 필요한 경우 아마도 아마도 아마도 아마도 아마 그렇지 않으면, 작은 물건을 땀을 흘리지 마십시오 (예 : 가치별로 통과하는 데 필요한 추가 N 지침)는 설계, 데이터 구조, 알고리즘 및 장기 유지 관리에 대한 걱정을합니다.

예, 참조는 괜찮습니다. 당신은 방법에 공유 소유권을 주려고하지 않습니다. 그것은 단지 그것으로 일하고 싶어합니다. 어쨌든 복사하기 때문에 첫 번째 케이스에 대한 참조도 할 수 있습니다. 그러나 첫 번째 경우 테이크 소유권. 여전히 한 번만 복사하는이 트릭이 있습니다.

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

반환 할 때도 복사해야합니다 (즉, 참조를 반환하지 않음). 수업은 클라이언트가 무엇을하고 있는지 알지 못하기 때문에 (포인터를 저장하고 Big Bang이 발생할 수 있습니다). 나중에 병목 현상 (첫 번째 프로파일!)이라는 것이 밝혀지면 참조를 반환 할 수 있습니다.


편집하다: 물론, 다른 사람들이 지적한 바와 같이, 이것은 당신이 당신의 코드를 알고 당신이 어떤 식 으로든 통과 된 공유 포인터를 재설정하지 않는다는 것을 알고 있다면 사실입니다. 의심스러운 경우 가치를 통과하십시오.

통과하는 것이 합리적입니다 shared_ptrs by const&. 그것은 문제를 일으키지 않을 것입니다 (참조가 가능하지 않은 경우를 제외하고 shared_ptr Earwicker가 자세히 설명하는대로 함수 호출 중에 삭제되며 많은 것을 통과하면 더 빠를 것입니다. 기억하다; 기본값 boost::shared_ptr 스레드 안전하므로 복사에는 스레드 안전 증분이 포함되어 있습니다.

사용하려고 노력하십시오 const& 단지보다는 &, 임시 객체는 정점이 아닌 참조로 전달되지 않을 수 있기 때문입니다. (MSVC의 언어 확장을 통해 어쨌든 할 수 있지만)

두 번째 경우, 이것을하는 것은 더 간단합니다.

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

당신은 그것을대로 부를 수 있습니다

only_work_with_sp(*sp);

함수가 명시 적으로 포인터를 수정할 수 없다면 "일반"참조를 피할 수 있습니다.

const & 작은 기능을 호출 할 때 합리적인 미세 최적화가 될 수 있습니다. 또한 스레드 안전이기 때문에 증분/감소는 동기화 지점입니다. 그래도 이것이 대부분의 시나리오에서 큰 차이를 만들 것으로 기대하지는 않습니다.

일반적으로 이유가 없다면 더 간단한 스타일을 사용해야합니다. 그런 다음 사용하십시오 const & 일관되게, 또는 몇 곳에서 사용하는 이유에 대한 의견을 추가하십시오.

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

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

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

그것은 조기 최적화에 관한 것이 아닙니다. 나는 당신이 원하는 일을 명확하게하고 코딩 관용구가 동료 개발자가 단단히 채택했을 때 CPU 사이클의 불필요한 폐기물을 피하는 것입니다.

단지 2 센트 :-)

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

예로 돌아가려면 :

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

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

진실은 shared_ptr, const인지 아닌지, 이것은 직접 또는 간접적으로 소유권을 공유하는 것과 같은 디자인 결함이있는 경우 일어날 수 있다는 것입니다. sp 스레드 사이에서, 그 객체를 missused delete this, 귀하는 원형 소유권 또는 기타 소유권 오류가 있습니다.

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

예를 들어이 코드는 오류가 발생하지만 변경하면 작동합니다. 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;
}

나는 당신이 익숙하다고 가정 할 것입니다 조기 최적화 그리고 학업 목적으로 또는 실적이 저조한 기존 코드를 격리했기 때문에 이것을 요구하고 있습니다.

참조로 통과하는 것은 괜찮습니다

Const 참조로 통과하는 것이 더 좋으며 일반적으로 가리키는 물체의 구성을 강요하지 않기 때문에 일반적으로 사용할 수 있습니다.

당신은 ~ 아니다 참조를 사용하여 포인터를 잃을 위험이 있습니다. 이 참조는 스택의 이전에 스마트 포인터 사본이 있고 하나의 스레드 만 호출 스택을 소유하고 있으므로 기존 사본이 사라지지 않도록 증거입니다.

참조를 사용합니다 자주 언급 한 이유에 대해 더 효율적입니다. 그러나 보장되지는 않습니다. 객체를 해석하는 것도 작동 할 수 있습니다. 당신의 이상적인 참조 사용 시나리오에는 코딩 스타일에 많은 작은 기능이 포함되면 포인터가 기능에서 기능으로 기능하기 전에 기능으로 전달됩니다.

당신은해야합니다 항상 저장을 피하십시오 참조로서의 스마트 포인터. 당신의 Class::take_copy_of_sp(&sp) 예는 이에 대한 올바른 사용법을 보여줍니다.

우리가 const 정확성에 관심이 없다고 가정하면 (또는 그 이상, 함수가 전달되는 데이터의 소유권을 수정하거나 공유 할 수 있음을 의미하는 경우) 부스트 :: shared_ptr을 전달하는 것은 다음과 같이 참조하여 전달하는 것보다 더 안전합니다. 우리는 원래 부스트 :: 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 's 데이터는 여전히 사용할 수 있습니다. 다른 개발자가 필연적으로 와서 피기 백을 시도하면 Shareda 's 깨지기 쉬운 존재, 혼돈이 뒤 따른다. 행운 Sharedb 그러나 개발자는 그의 세상에 모든 것이 옳기 때문에 일찍 집으로갑니다.

이 경우 코드 안전은 속도 개선 복사를 훨씬 능가합니다. 동시에 Boost :: Shared_PTR은 코드 안전을 향상시키기위한 것입니다. 이런 종류의 틈새 최적화가 필요한 경우 사본에서 참조로 이동하는 것이 훨씬 쉽습니다.

샌디는 다음과 같이 썼다.

어느 정도는 사실이지만 shared_ptr을 사용하는 점은 객체 수명에 대한 우려를 제거하고 컴파일러가이를 처리하도록하는 것입니다. 참조로 공유 포인터를 통과시키고 객체 데이터를 제거 할 수있는 참조 카운트-객체 호출 호출을 허용하는 경우 공유 포인터를 사용하는 것은 거의 무의미합니다.

나는 이전 문장에서 성능이 우려 될 수 있기 때문에 "거의"썼고, 드문 경우에는 정당화 될 수 있지만,이 시나리오를 직접 피하고 심각하게 볼 수있는 다른 모든 최적화 솔루션을 직접 찾을 것입니다. 다른 수준의 간접, 게으른 평가 등을 추가 할 때 ..

과거의 저자 또는 저자의 메모리를 게시하는 코드, 행동에 대한 암시 적 가정, 특히 물체 수명에 대한 행동에 대한 암시 적 가정이 필요하며, 명확하고 간결하며 읽기 쉬운 문서가 필요하며 많은 고객이 그것을 읽지 않을 것입니다! 단순성은 거의 항상 효율성을 우선시하며, 효율적이라는 다른 방법은 거의 항상 있습니다. 참조 카운터-객체 (및 평등 연산자)의 복사 생성자에 의한 깊은 복사를 피하기 위해 참조로 값을 전달 해야하는 경우, 심도있는 데이터를 참조 계산 포인터로 만드는 방법을 고려해야합니다. 빠르게 복사. (물론, 그것은 당신의 상황에 적용되지 않을 수있는 하나의 디자인 시나리오 일뿐입니다).

나는 프로젝트에서 똑똑한 포인터를 가치별로 통과시키는 것에 대해 매우 강력하다는 프로젝트에서 일했습니다. 성능 분석을 요청 받았을 때 - 스마트 포인터의 기준 카운터의 증가 및 감소를 위해 응용 프로그램은 사용 된 프로세서 시간의 4-6% 사이를 소비한다는 것을 알았습니다.

Daniel Earwicker의 설명대로 이상한 경우에 문제를 피하기 위해 가치로 스마트 포인터를 전달하려면 지불하는 가격을 이해해야합니다.

참조를 사용하기로 결정한 경우 Const Reference를 사용하는 주된 이유는 인터페이스에서 사용하는 클래스를 상속하는 클래스에서 공유 포인터를 전달해야 할 때 암시 적 업 캐스트를 수행 할 수 있도록하는 것입니다.

Litb가 말한 것 외에도 두 번째 예에서는 Const 참조를 통과해야한다고 지적하고 싶습니다. 실수로 수정하지 않는다고 확신합니다.

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;
    }
    

모든 코드 조각에는 어떤 의미가 있어야 합니다.공유 포인터를 값으로 전달하는 경우 어디에나 응용 프로그램에서 이는 다음을 의미합니다. "다른 곳에서 무슨 일이 일어나고 있는지 확신할 수 없기 때문에 순수한 안전을 선호합니다.".이것은 코드를 참조할 수 있는 다른 프로그래머에게 좋은 신뢰 신호라고 부르는 것이 아닙니다.

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

예, IMO에서는 기본값은 "const 참조로 전달".

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top