공유 포인터를 인수로 전달


88

공유 포인터로 래핑 된 객체를 선언하면 :

std::shared_ptr<myClass> myClassObject(new myClass());

그런 다음 메서드에 인수로 전달하고 싶었습니다.

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

위의 내용은 단순히 shared_pt의 참조 횟수를 증가시키고 모든 것이 멋지나요? 아니면 매달린 포인터를 남기나요?

그래도 이렇게해야합니까? :

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

나는 두 번째 방법이 (전체 스마트 포인터와 달리) 하나의 주소 만 복사해야하기 때문에 더 효율적일 수 있다고 생각하지만 첫 번째 방법은 더 읽기 쉽고 성능 제한을 밀어 붙일 것으로 예상하지 않습니다. 나는 그것에 대해 위험한 것이 없는지 확인하고 싶습니다.

감사합니다.


14
const std::shared_ptr<myClass>& arg1
캡틴 Obvlious

3
두 번째 방법은 깨졌습니다. 첫 번째 방법은 실제로 소유권을 공유하기 위해 기능이 필요한 경우 관용적입니다. 그러나 DoSomething정말로 소유권을 공유해야합니까? 대신 참조를 받아야하는 것 같습니다 ...
ildjarn

8
@SteveH : 그렇지 않지만 실제로 필요하지 않은 경우 함수가 호출자에게 특수 객체 소유권 의미를 강제해야하는 이유는 무엇입니까? 귀하의 함수는 void DoSomething(myClass& arg1).
ildjarn

2
@SteveH : 스마트 포인터의 전체 목적은 일반적인 개체 소유권 문제를 처리하는 것입니다. ;-] shared_ptr<>구체적 으로 말하자면, 실제로 소유권을 공유하려면 가치를 전달 해야합니다 .
ildjarn

4
관련 없음 : 일반적으로 생성자 보다 std::make_shared더 효율적일뿐만 아니라 안전 합니다 std::shared_ptr.
R. Martinho Fernandes

답변:


171

함수에 공유 포인터를 전달하고 싶습니다. 저를 도와 줄 수 있습니까?

물론입니다. 도와 드릴 수 있습니다. 나는 당신이 C ++의 소유권 의미에 대해 어느 정도 이해하고 있다고 가정합니다. 사실인가요?

예, 저는 주제에 대해 상당히 편안합니다.

좋은.

좋아, 나는 shared_ptr논쟁을 해야하는 두 가지 이유만을 생각할 수있다 .

  1. 함수는 객체의 소유권을 공유하려고합니다.
  2. 이 함수는 shared_ptrs에서 특별히 작동하는 일부 작업을 수행합니다 .

어떤 것에 관심이 있습니까?

나는 일반적인 대답을 찾고 있으므로 실제로 두 가지 모두에 관심이 있습니다. 그래도 케이스 # 2에서 당신이 의미하는 바가 궁금합니다.

이러한 함수의 예로는 std::static_pointer_cast, 사용자 정의 비교기 또는 술어가 있습니다. 예를 들어 벡터에서 고유 한 shared_ptr을 모두 찾아야하는 경우 이러한 술어가 필요합니다.

아, 함수가 실제로 스마트 포인터 자체를 조작해야 할 때.

바로 그거죠.

그럴 때는 참고로 통과해야한다고 생각합니다.

예. 포인터를 변경하지 않으면 const 참조로 전달하고 싶습니다. 소유권을 공유 할 필요가 없기 때문에 복사 할 필요가 없습니다. 그것이 다른 시나리오입니다.

알겠습니다. 다른 시나리오에 대해 이야기합시다.

소유권을 공유하는 사람? 확인. 어떻게 소유권을 공유 shared_ptr합니까?

그것을 복사함으로써.

그런 다음 함수는 shared_ptr, 맞습니까?

명백하게. 그래서 나는 그것을 const에 대한 참조로 전달하고 지역 변수에 복사합니까?

아니, 그것은 비관적이다. 참조로 전달되면 함수는 수동으로 복사 할 수밖에 없습니다. 값으로 전달되면 컴파일러는 복사와 이동 중에서 최상의 선택을 선택하고 자동으로 수행합니다. 따라서 가치를 전달하십시오.

좋은 지적. " 속도를 원하십니까? 가치로 전달 "이라는 기사를 더 자주 기억해야합니다 .

shared_ptr예를 들어 함수 가 멤버 변수에를 저장하면 어떻게 될까요? 중복 사본을 만들지 않습니까?

이 함수는 단순히 shared_ptr인수를 저장소로 이동할 수 있습니다 . 이동은 shared_ptr참조 횟수를 변경하지 않기 때문에 저렴합니다.

아, 좋은 생각이야.

하지만 세 번째 시나리오를 생각하고 있습니다.을 조작 shared_ptr하거나 소유권을 공유 하지 않으려면 어떻게해야 합니까?

이 경우 기능과 shared_ptr는 전혀 관련이 없습니다. pointee를 조작하려면 pointee를 가져 와서 호출자가 원하는 소유권 의미를 선택하도록합니다.

그리고 참조 또는 가치로 pointee를 취해야합니까?

일반적인 규칙이 적용됩니다. 스마트 포인터는 아무것도 변경하지 않습니다.

복사하려면 값으로 전달하고 복사를 피하려면 참조로 전달하십시오.

권리.

흠. 또 다른 시나리오를 잊은 것 같습니다. 소유권을 공유하고 싶지만 특정 조건에만 의존하는 경우 어떻게합니까?

아, 흥미로운 엣지 케이스. 나는 그것이 자주 일어날 것이라고 기대하지 않습니다. 그러나 그것이 발생하면 값으로 전달하고 필요하지 않으면 사본을 무시하거나 참조로 전달하고 필요한 경우 사본을 만들 수 있습니다.

첫 번째 옵션에서 중복 사본 하나를 위험에 빠뜨리고 두 번째 옵션에서는 잠재적 인 이동을 잃습니다. 케이크도 먹어도 안 돼요?

이것이 정말로 중요한 상황에 있다면 두 개의 오버로드를 제공 할 수 있습니다. 하나는 const lvalue 참조를 사용하고 다른 하나는 rvalue 참조를 사용합니다. 하나는 복사하고 다른 하나는 움직입니다. 완벽한 전달 기능 템플릿은 또 다른 옵션입니다.

가능한 모든 시나리오를 포함한다고 생각합니다. 대단히 감사합니다.


2
@Jon : 무엇을 위해? 이것은 내가 A를 해야하는지 B를 해야하는지에 관한 것 입니다. 우리 모두 값 / 참조로 객체를 전달하는 방법을 알고 있다고 생각합니다.
sbi 2012-06-07

9
"그렇게하면 사본을 만들기 위해 const에 대한 참조로 전달한다는 뜻입니까? 아니요, 비관적입니다."와 같은 산문이 초보자에게는 혼란 스럽기 때문입니다. const에 대한 참조를 전달하면 복사본이 생성되지 않습니다.
Jon

1
@Martinho 나는 "당신이 pointee를 조작하고 싶다면, pointee를 잡아라"라는 규칙에 동의하지 않는다. 답변 에서 언급했듯이 객체의 임시 소유권을 취하지 않으면 위험 할 수 있습니다. 내 생각에 기본값은 함수 호출 기간 동안 개체 유효성을 보장하기 위해 임시 소유권에 대한 복사본을 전달하는 것입니다.
radman 2013 년

@radman 답변에 해당 규칙을 적용하는 시나리오가 없으므로 완전히 관련이 없습니다. a에서 참조로 전달하고 shared_ptr<T> ptr;(즉 void f(T&),로 호출 됨 f(*ptr)) 객체가 호출보다 오래 지속되지 않는 SSCCE를 작성해주세요 . 또는 가치 void f(T)와 문제가 발생 하는 곳을 작성하십시오 .
R. Martinho Fernandes 2013 년

@Martinho 는 간단한 자체 포함 올바른 예제에 대한 예제 코드 를 확인하십시오 . 예제는 void f(T&)스레드에 대한 것이며 관련됩니다. 내 문제는 귀하의 답변이 가장 안전하고 직관적이며 가장 복잡한 사용 방법 인 shared_ptr의 소유권을 전달하지 못하게한다는 것입니다. 나는 초보자에게 shared_ptr <> 소유의 데이터에 대한 참조를 추출하도록 조언하는 것은 극도로 반대 할 것입니다. 오용 가능성이 크고 이점은 최소화됩니다.
radman 2013 년

22

사람들은 원시 포인터를 함수 매개 변수로 사용하는 것을 불필요하게 두려워한다고 생각합니다. 함수가 포인터를 저장하지 않거나 수명에 영향을주지 않으면 원시 포인터도 마찬가지로 작동하며 가장 낮은 공통 분모를 나타냅니다. 예를 들어 값 또는 const 참조로를 매개 변수 unique_ptr로 사용하는 함수 에을 전달하는 방법을 고려해보십시오 shared_ptr.

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

함수 매개 변수로서의 원시 포인터는 실제로 중요한 호출 코드에서 스마트 포인터를 사용하는 것을 방해하지 않습니다.


8
포인터를 사용하는 경우 참조 만 사용하지 않는 이유는 무엇입니까? DoSomething(*a_smart_ptr)
Xeo

5
@Xeo, 당신 말이 맞아요. 때로는 NULL 포인터의 가능성을 허용해야합니다.
Mark Ransom

1
원시 포인터를 출력 매개 변수로 사용하는 규칙이있는 경우, 호출 코드에서 변수가 변경 될 수있는 것을 발견하는 것이 더 쉽습니다 . 예 : compare fun(&x)to fun(x). 후자의 예에서는 x값으로 또는 const ref로 전달 될 수 있습니다. 위에서 언급 한대로 nullptr출력에 관심이없는 경우 통과 할 수도 있습니다 ...
Andreas Magnusson

2
@AndreasMagnusson : 출력 매개 변수로서의 포인터 규칙은 다른 소스에서 전달 된 포인터를 다룰 때 잘못된 보안 감각을 줄 수 있습니다. 이 경우 호출이 fun (x)이고 * x가 수정되고 소스에 경고 할 & x가 없기 때문입니다.
Zan Lynx

함수 호출이 호출자의 범위를 초과하면 어떻게 될까요? 공유 ptr의 소멸자가 호출되었을 가능성이 있으며 이제 함수가 삭제 된 메모리에 액세스하려고 시도하여 UB
Sridhar Thiagarajan

4

예, shared_ptr <>에 대한 전체 아이디어는 여러 인스턴스가 동일한 원시 포인터를 보유 할 수 있으며 shared_ptr <>의 마지막 인스턴스가 파괴 될 때만 기본 메모리가 해제된다는 것입니다.

이제 raw_pointers를 다시 다루고 있기 때문에 shared_ptr <>에 대한 포인터를 피할 것입니다.


2

첫 번째 예에서 가치로 전달하는 것은 안전하지만 더 나은 관용구가 있습니다. 가능하면 const 참조로 전달하십시오-스마트 포인터를 다룰 때도 예라고 말할 것입니다. 두 번째 예는 정확히 깨진 것은 아니지만 매우 !???. 어리석은 일, 아무것도 달성하지 못하고 스마트 포인터의 일부를 무너 뜨리고, 일을 역 참조하고 수정하려고 할 때 괴로움의 세계에 빠지게됩니다.


3
원시 포인터를 의미하는 경우 아무도 포인터 전달을 옹호하지 않습니다. 소유권 분쟁이없는 경우 참조로 전달하는 것은 확실히 관용적입니다. 요점은 관련이며, shared_ptr<>당신이 있음을 구체적으로 해야한다 실제로 지분하기 위해 복사본을 만들; 에 대한 참조 또는 포인터가 shared_ptr<>있으면 아무것도 공유하지 않으며 일반 참조 또는 포인터와 동일한 수명 문제가 발생합니다.
ildjarn

2
@ildjarn :이 경우 상수 참조를 전달하는 것이 좋습니다. shared_ptr호출자 의 원본 은 함수 호출보다 오래 지속되므로 함수는 shared_ptr<> const &. 나중에 포인터를 저장해야하는 경우 복사해야합니다 (이 시점에서 참조를 유지하는 대신 복사해야 함). 필요한 경우가 아니면 복사 비용이 발생 하지 않습니다. 그것. 이 경우에는 일정한 참조를 전달하겠습니다 ....
David Rodríguez-dribeas

3
@David : " 이 경우 상수 참조를 전달하는 것은 괜찮습니다. "정상적인 API에서는 아닙니다 . API가 일반 const 참조를 취하는 것보다 활용하지 않는 스마트 포인터 유형을 요구하는 이유는 무엇입니까? 그것은 const-correctness의 부족만큼이나 나쁩니다. 당신의 요점이 기술적으로 아무것도 아프지 않다는 것이라면, 나는 확실히 동의하지만 그것이 옳은 일이라고 생각하지 않습니다.
ildjarn

2
... 반면에 함수가 객체의 수명을 연장 할 필요가 없다면 shared_ptr인터페이스에서를 제거 const하고 뾰족한 객체에 일반 ( ) 참조를 전달할 수 있습니다 .
David Rodríguez-dribeas

3
@David : API 소비자가 처음 shared_ptr<>부터 사용하지 않는 경우 어떻게 합니까? 이제 API는 호출자가 객체 수명 의미 체계를 사용하기 위해 무의미하게 변경하도록 강요하므로 목에 통증이 있습니다. 나는 "기술적으로 어떤 것도 해를 끼치 지 않는다"고 말하는 것 외에는 옹호할만한 가치가 없다고 본다. 나는 '공유 소유권이 필요하지 않으면 사용하지 마십시오'에 대해 논란의 여지가 없습니다 shared_ptr<>.
ildjarn

0

함수 DoSomething 에서 클래스 인스턴스의 데이터 멤버를 변경 myClass 하므로 수정하는 것은 (shared_ptr) 개체가 아닌 관리되는 (원시 포인터) 개체입니다. 즉,이 함수의 반환 지점에서 관리되는 원시 포인터에 대한 모든 공유 포인터가 데이터 멤버를 보게 myClass::someField됩니다. 다른 값으로 변경됩니다.

이 경우 객체를 수정하지 않는다는 보장과 함께 함수에 객체를 전달합니다 (소유 객체가 아닌 shared_ptr에 대해 이야기).

이것을 표현하는 관용구는 다음과 같습니다.

void DoSomething(const std::shared_ptr<myClass>& arg)

마찬가지로 함수의 사용자에게 원시 포인터의 소유자 목록에 다른 소유자를 추가하지 않도록 보장합니다. 그러나 원시 포인터가 가리키는 기본 개체를 수정할 수있는 가능성을 유지했습니다.

주의 : 즉, 누군가 shared_ptr::reset함수를 호출 하기 전에 누군가 호출 하고 그 순간에 raw_ptr을 소유 한 마지막 shared_ptr이면 객체가 파괴되고 함수가 매달려있는 포인터를 조작하여 객체를 파괴한다는 의미입니다. 매우 위험한!!!

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