참조 또는 값으로 스마트 포인터 (shared_ptr)를 반환하는 방법은 무엇입니까?


94

을 반환하는 메서드가있는 클래스가 있다고 가정 해 보겠습니다 shared_ptr.

참조 또는 가치로 반품 할 경우 가능한 이점과 단점은 무엇입니까?

두 가지 가능한 단서 :

  • 초기 개체 파괴. shared_ptrby (const) 참조를 반환하면 참조 카운터가 증가하지 않으므로 다른 컨텍스트 (예 : 다른 스레드)에서 범위를 벗어날 때 개체가 삭제 될 위험이 있습니다. 이 올바른지? 환경이 단일 스레드 인 경우이 상황도 발생할 수 있습니까?
  • 비용. 가치에 의한 전달은 확실히 무료가 아닙니다. 가능할 때마다 피할 가치가 있습니까?

모든 분에게 감사합니다.

답변:


114

값으로 스마트 포인터를 반환합니다.

말했듯이 참조로 반환하면 참조 횟수가 제대로 증가하지 않아 부적절한 시간에 무언가를 삭제할 위험이 있습니다. 그것만으로도 참조로 반환하지 않을 충분한 이유가 될 것입니다. 인터페이스는 견고해야합니다.

비용 문제는 현재 RVO ( Return Value Optimization) 덕분에 문제가되고 있으므로 최신 컴파일러에서 증가-증가-감소 시퀀스 또는 이와 유사한 것이 발생하지 않습니다. 따라서 a를 반환하는 가장 좋은 방법 shared_ptr은 단순히 값으로 반환하는 것입니다.

shared_ptr<T> Foo()
{
    return shared_ptr<T>(/* acquire something */);
};

이것은 최신 C ++ 컴파일러에 대한 분명한 RVO 기회입니다. Visual C ++ 컴파일러는 모든 최적화가 꺼져 있어도 RVO를 구현한다는 사실을 알고 있습니다. 그리고 C ++ 11의 이동 의미론을 사용하면이 문제는 훨씬 덜 관련성이 있습니다. (하지만 확실한 방법은 프로필을 작성하고 실험하는 것뿐입니다.)

여전히 확신이 서지 않는다면, Dave Abrahams는 가치 반환에 대한 논쟁을 하는 기사 를 가지고 있습니다. 여기에 스 니펫을 재현합니다. 나는 당신이 전체 기사를 읽는 것이 좋습니다.

솔직히 말해서 다음 코드가 기분이 어떤가요?

std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();

솔직히 더 잘 알아야하는데 긴장이된다. 원칙적으로, 경우에 get_names() 반환, 우리가 가지고 복사 vectorstring들. 그런 다음 초기화 할 때 다시 복사 names해야하고 첫 번째 복사본을 삭제해야합니다. string벡터에 N이있는 경우 각 복사본에는 문자열 내용이 복사 될 때마다 N + 1 메모리 할당과 캐시에 비 친화적 인 데이터 액세스가 많이 필요할 수 있습니다.

그런 종류의 불안에 맞서기보다는 불필요한 사본을 피하기 위해 참조에 의한 전달을 자주 사용합니다.

get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );

불행히도이 접근 방식은 이상적이지 않습니다.

  • 코드가 150 % 증가했습니다.
  • 우리는 const이름을 변경하기 때문에 -ness 를 떨어 뜨려야했습니다.
  • 함수형 프로그래머가 우리에게 상기시키고 싶어하는 것처럼, 돌연변이는 참조 투명성과 방정식 추론을 약화시켜 코드를 추론하기 더 복잡하게 만듭니다.
  • 더 이상 이름에 대한 엄격한 값 의미 체계가 없습니다.

그러나 효율성을 얻기 위해 이런 식으로 코드를 엉망으로 만드는 것이 정말로 필요합니까? 다행히도 대답은 '아니오'로 판명되었습니다 (특히 C ++ 0x를 사용하는 경우에는 그렇지 않습니다).


참조로 돌아 오면 확실히 RVO가 불가능하기 때문에 RVO가 의문의 여지가 있다고 말할 수는 없습니다.
Edward Strange

@CrazyEddie : 사실, 이것이 OP가 가치로 반환되도록 권장하는 이유 중 하나입니다.
In silico

표준에서 허용하는 RVO 규칙이 표준에서 보장하는 동기화 / 전 발생 관계에 대한 규칙보다 우선합니까?
edA-qa mort-ora-y

1
@ edA-qa mort-ora-y : RVO는 부작용이 있더라도 명시 적으로 허용됩니다. 예를 들어 cout << "Hello World!";기본 및 복사 생성자에 명령문이있는 Hello World!경우 RVO가 적용될 때 두 개의 s 가 표시되지 않습니다 . 그러나 이것은 올바르게 설계된 스마트 포인터, 심지어 wrt 동기화에 대해서는 문제가되지 않아야합니다.
In silico

23

어떤 스마트 포인터 (shared_ptr뿐만 아니라) 에 관해서도 참조를 반환하는 것이 허용되지 않는다고 생각하며 참조 또는 원시 포인터로 전달하는 것을 매우 주저합니다. 왜? 나중에 참조를 통해 얕은 복사되지 않을지 확신 할 수 없기 때문입니다. 첫 번째 요점은 이것이 우려되어야하는 이유를 정의합니다. 이는 단일 스레드 환경에서도 발생할 수 있습니다. 프로그램에 잘못된 복사본 의미를 적용하기 위해 데이터에 대한 동시 액세스가 필요하지 않습니다. 포인터를 전달한 후에는 사용자가 포인터로 수행하는 작업을 실제로 제어 할 수 없으므로 API 사용자에게 충분한 로프를 제공하는 오용을 조장하지 마십시오.

둘째, 가능하면 스마트 포인터의 구현을 살펴보십시오. 건설과 파괴는 무시할 수있는 수준에 가까워 야합니다. 이 오버 헤드가 허용되지 않으면 스마트 포인터를 사용하지 마십시오! 그러나이 외에도 포인터의 사용을 추적하는 메커니즘에 대한 상호 배타적 인 액세스가 shared_ptr 개체의 단순한 구성보다 속도를 더 느리게 할 것이기 때문에 현재 가지고있는 동시성 아키텍처도 검토해야합니다.

편집, 3 년 후 : C ++의 더 현대적인 기능의 출현으로, 호출 함수의 범위를 벗어나지 않는 람다를 작성했을 때의 경우를 더 수용하도록 내 대답을 수정했습니다. 다른 곳에 복사했습니다. 여기서 공유 포인터를 복사하는 데 드는 최소한의 오버 헤드를 절약하려면 공정하고 안전합니다. 왜? 참조가 오용되지 않도록 보장 할 수 있기 때문입니다.

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