함수에서 unique_ptr 반환


367

unique_ptr<T>복사 구성을 허용하지 않고 대신 이동 의미를 지원합니다. 그러나 unique_ptr<T>함수에서를 반환하고 반환 된 값을 변수에 할당 할 수 있습니다 .

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

위의 코드는 의도 한대로 컴파일되고 작동합니다. 그렇다면 어떻게 그 줄 1이 복사 생성자를 호출하지 않고 컴파일러 오류가 발생합니까? 2대신 줄을 사용해야하는 경우 의미가 있습니다 (줄을 사용 2하는 것도 좋지만 그렇게 할 필요는 없습니다).

C ++ 0x는 unique_ptr반환 값이 함수가 종료되는 즉시 파괴되어 반환 포인터의 고유성을 보장하기 때문에 임시 객체 이므로이 예외를 허용 한다는 것을 알고 있습니다. 이것이 어떻게 구현되는지 궁금합니다. 컴파일러에서 특수한 경우입니까 아니면 언어 사양에 다른 악용 조항이 있습니까?


가설 적으로 팩토리 메소드를 구현하는 경우 팩토리의 출력을 리턴하기 위해 1 또는 2를 선호합니까? 적절한 팩토리를 사용하면 실제로 생성 된 객체의 소유권이 호출자에게 전달되기를 원하기 때문에 이것이 1의 가장 일반적인 용도 일 것으로 추정됩니다.
Xharlie

7
@Xharlie? 둘 다의 소유권을 전달합니다 unique_ptr. 전체 질문은 1과 2가 동일한 것을 달성하는 두 가지 다른 방법이라는 것입니다.
Praetorian

이 경우 RVO는 c ++ 0x에서도 발생하며 unique_ptr 객체의 소멸은 main함수 종료 후 한 번 수행 되지만 종료시에는 수행 되지 않습니다 foo.
ampawd

답변:


218

언어 사양에 이것이 악용되는 다른 조항이 있습니까?

예, 12.8 §34 및 §35 참조 :

특정 조건이 충족 될 때, 구현이 클래스 객체의 복사 / 이동 공사를 생략 할 수있다 [...] 복사 / 이동 작업이 생략라는 복사 생략이 허용됩니다 [...] return 문에서의 표현식이 함수 반환 유형과 동일한 cv-unqualified 유형 을 갖는 비 휘발성 자동 객체의 이름 인 경우 클래스 반환 유형을 갖는 함수 [...]

복사 조작 제거 기준이 충족되고 복사 될 오브젝트가 lvalue 로 지정되면, 오브젝트가 rvalue로 지정된 것처럼 복사의 생성자를 선택하기위한 과부하 해결이 먼저 수행 됩니다 .


최악의 경우, 즉 C ++ 11, C ++ 14 및 C ++ 17의 소거가없는 경우 return 문에 명명 된 값이 처리되므로 값으로 반환하는 것이 기본 선택이어야한다는 점을 하나 더 추가하고 싶었습니다. rvalue로. 예를 들어 다음 함수는 -fno-elide-constructors플래그를 사용하여 컴파일합니다.

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

컴파일시 플래그가 설정되면이 기능에서 두 번의 이동 (1과 2)이 발생하고 나중에 한 번의 이동 (3)이 발생합니다.


@juanchopanza foo()함수 내에서 반환 값과 마찬가지로 본질적 으로 파괴 되기를 원한다는 것을 의미 unique_ptr<int> p = foo();합니까?
7cows

1
이 답변은 구현 무언가를 할 있다고 말합니다 ... 그렇다고 말하지는 않습니다.이 섹션이 유일한 관련 섹션이라면이 동작에 의존하는 것은 이식성이 없다는 것을 의미합니다. 그러나 나는 그것이 옳다고 생각하지 않습니다. Nikola Smiljanic과 Bartosz Milewski의 답변에서 설명한 것처럼 정답이 이동 생성자와 더 관련이 있다고 생각합니다.
Don Hatch

6
@DonHatch이 경우 복사 / 이동 제거를 수행하는 것이 "허용"되었다고 말하지만 여기서는 복사 제거에 대해 이야기하지 않습니다. 여기에 적용되는 두 번째 인용문으로, 복사 제거 규칙에 대한 피기 백이지만 복사 제거 자체는 아닙니다. 두 번째 단락에는 불확실성이 없으며 완전히 이식 가능합니다.
Joseph Mansfield

@juanchopanza 나는 이것이 2 년 후에 지금 깨달았지만 여전히 이것이 잘못되었다고 생각합니까? 이전 의견에서 언급했듯이 이것은 복사 제거에 관한 것이 아닙니다. 복사 제거가 적용될 수있는 경우 (로 적용 할 수없는 경우에도 std::unique_ptr) 먼저 객체를 rvalue로 처리하는 특별한 규칙이 있습니다. 나는 이것이 Nikola가 대답 한 것에 전적으로 동의한다고 생각합니다.
Joseph Mansfield

1
따라서 왜이 예제와 정확히 같은 방식으로 반환 할 때 이동 전용 유형 (제거 된 복사 생성자)에 대해 "삭제 된 함수를 참조하려고 시도"오류가 계속 발생합니까?
DrumM

104

이는 특정 방식이 std::unique_ptr아니지만 이동 가능한 모든 클래스에 적용됩니다. 값으로 반환되므로 언어 ​​규칙에 의해 보장됩니다. 컴파일러는 복사본을 제거하려고 시도하고 복사본을 제거 할 수 없으면 이동 생성자를 호출하고 이동할 수 없으면 복사 생성자를 호출하며 복사 할 수 없으면 컴파일에 실패합니다.

std::unique_ptr인수로 받아들이는 함수가 있으면 p를 전달할 수 없습니다. 이동 생성자를 명시 적으로 호출해야하지만이 경우 호출 후 변수 p를 사용해서는 안됩니다 bar().

void bar(std::unique_ptr<int> p)
{
    // ...
}

int main()
{
    unique_ptr<int> p = foo();
    bar(p); // error, can't implicitly invoke move constructor on lvalue
    bar(std::move(p)); // OK but don't use p afterwards
    return 0;
}

3
@Fred-글쎄요. 하지만 p결과, 임시 아니다 foo()반환되는 무엇을,이다; 따라서 rvalue이고 이동할 수 있으므로 할당이 main가능합니다. 니콜라 p는 오류가있는 이 규칙을 적용하는 것 외에는 당신이 틀렸다고 말하고 싶습니다 .
Edward Strange

정확히 내가 말하고 싶지만 단어를 찾을 수 없었습니다. 답변이 명확하지 않기 때문에 해당 부분을 삭제했습니다.
Nikola Smiljanić

질문이 있습니다. 원래 질문에서 Line 1과 Line 사이에 실질적인 차이가 2있습니까? 내 견해로 p는 in을 구성 할 때 main반환 유형의 유형에만 관심이 있기 때문에 동일합니다 foo.
Hongxu Chen

1
@HongxuChen이 예에서는 전혀 차이가 없습니다. 허용 된 답변에서 표준의 인용문을 참조하십시오.
Nikola Smiljanić

실제로, p를 할당하는 한 나중에 p를 사용할 수 있습니다. 그때까지는 내용을 참조 할 수 없습니다.
Alan

38

unique_ptr에는 전통적인 복사 생성자가 없습니다. 대신 rvalue 참조를 사용하는 "이동 생성자"가 있습니다.

unique_ptr::unique_ptr(unique_ptr && src);

rvalue 참조 (이중 앰퍼샌드)는 rvalue에만 바인딩됩니다. 그래서 lvalue unique_ptr을 함수에 전달하려고 할 때 오류가 발생하는 이유입니다. 반면에 함수에서 반환 된 값은 rvalue로 취급되므로 이동 생성자가 자동으로 호출됩니다.

그건 그렇고, 이것은 올바르게 작동합니다 :

bar(unique_ptr<int>(new int(44));

여기서 임시 unique_ptr은 rvalue입니다.


8
요점은 더 중요하다고 생각합니다. 왜 p"명확하게" lvalue 는의 정의에서 return 문 에서 rvalue 로 취급 될 수 있습니다 . 함수 자체의 반환 값을 "이동"할 수 있다는 사실에는 문제가 없다고 생각합니다. return p;foo
CB Bailey

함수에서 반환 된 값을 std :: move로 줄 바꿈하면 두 번 이동한다는 의미입니까?

3
@RodrigoSalazar std :: move는 lvalue 참조 (&)에서 rvalue 참조 (&&)로 변환 된 것입니다. rvalue 참조에서 std :: move의 불필요한 사용법은 단순히 noop 일 것입니다
TiMoch

13

Scott Meyers의 Effective Modern C ++ 항목 25 에 완벽하게 설명되어 있다고 생각합니다 . 발췌문은 다음과 같습니다.

RVO의 표준 축복의 일부는 RVO의 조건이 충족되지만 컴파일러가 복사 제거를 수행하지 않기로 선택한 경우 반환되는 객체는 rvalue로 처리되어야한다고 말합니다. 사실상, 표준은 RVO가 허용 될 때 복사 제거가 발생하거나 std::move반환되는 로컬 객체에 암시 적으로 적용되도록 요구합니다.

여기에, 망막 정맥 폐쇄는 를 의미 값 최적화를 반환 하고, 망막 정맥 폐쇄의 조건이 충족되는 경우 로컬 객체를 반환하는 방법을 당신이 어떻게 기대하는 함수 내에서 선언 된 RVO 잘 참조하여 자신의 책의 항목 25에서 설명도, 표준 (여기서 로컬 오브젝트 에는 return 문으로 작성된 임시 오브젝트가 포함됨) 발췌에서 가장 큰 부분은 복사 제거가 발생하거나 std::move반환되는 로컬 객체에 암시 적으로 적용되는 것 입니다. Scott은 항목 25 std::move에서 컴파일러가 복사본을 제거하지 않기로 선택하고 프로그래머가 명시 적으로 그렇게하지 않아야 할 때 암시 적으로 적용되는 항목을 언급합니다 .

귀하의 경우 코드는 로컬 객체를 반환 하고 유형은 반환 유형과 동일하므로 RVO 의 후보입니다 . 그리고 컴파일러가 어떤 이유로 든 복사본을 제거하지 않기로 선택 하면 line에 들어갔을 것 입니다.ppstd::move1


5

다른 답변에서 보지 못한 것 중 하나는다른 답변 을 명확하게하기 위해 함수 내에서 생성 된 std :: unique_ptr 반환과 해당 함수에 부여 된 std :: unique_ptr 사이에 차이가 있음 을 분명히 합니다.

예를 들면 다음과 같습니다.

class Test
{int i;};
std::unique_ptr<Test> foo1()
{
    std::unique_ptr<Test> res(new Test);
    return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
    // return t;  // this will produce an error!
    return std::move(t);
}

//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));

그것은 fredoverflow에 의해 답변 에서 언급되었습니다 -명확하게 강조 표시된 " 자동 객체". 참조 (rvalue 참조 포함)는 자동 객체가 아닙니다.
Toby Speight

@TobySpeight 좋아요, 죄송합니다. 내 코드는 설명 일뿐입니다.
v010dya
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.