보증 복사 제거는 어떻게 작동합니까?


90

2016 Oulu ISO C ++ 표준 회의에서 단순화 된 값 범주를 통한 복사 제거 보장 이라는 제안 이 표준위원회에 의해 C ++ 17로 투표되었습니다.

보장 된 복사 제거는 정확히 어떻게 작동합니까? 복사 제거가 이미 허용 된 일부 경우를 포함합니까, 아니면 복사 제거를 보장하기 위해 코드를 변경해야합니까?

답변:


130

여러 상황에서 복사 제거가 허용되었습니다. 그러나 허용 되더라도 코드는 사본이 제거되지 않은 것처럼 작동 할 수 있어야했습니다. 즉, 액세스 가능한 복사 및 / 또는 이동 생성자가 있어야합니다.

보장 된 복사 제거는 복사 / 이동이 제거 될 수있는 특정 상황이 실제로 복사 / 이동 을 전혀 유발하지 않도록 여러 C ++ 개념을 재정의 합니다 . 컴파일러는 복사본을 제거하지 않습니다. 표준은 그러한 복사가 결코 일어날 수 없다고 말합니다.

이 기능을 고려하십시오.

T Func() {return T();}

비 보장 복사 제거 규칙에 따라 임시를 만든 다음 해당 임시에서 함수의 반환 값으로 이동합니다. 그 이동 작업 생략 수 있지만T 사용되지 않는 경우에도 액세스 가능한 이동 생성자가 있어야합니다.

비슷하게:

T t = Func();

의 복사 초기화입니다 t. 이것은 t의 반환 값으로 초기화 를 복사 Func합니다. 그러나 T호출되지 않더라도 이동 생성자가 있어야합니다.

보장 된 복사 제거 는 prvalue 표현식의 의미를 재정의합니다 . C ++ 17 이전, prvalue는 임시 개체입니다. C ++ 17에서 prvalue 표현식은 일시적인 것을 구체화 할 수있는 것일 뿐이지 만 아직 임시적인 것은 아닙니다.

prvalue를 사용하여 prvalue 유형의 객체를 초기화하면 임시가 구체화되지 않습니다. 할 때 return T();prvalue를 통해 함수의 반환 값을 초기화합니다. 해당 함수가를 반환하므로 T임시가 생성되지 않습니다. prvalue의 초기화는 단순히 반환 값을 직접 초기화합니다.

이해해야 할 것은 반환 값이 prvalue이므로 아직 객체아니라는 것입니다 . 객체의 이니셜 라이저 일뿐 T()입니다.

그렇게 T t = Func();하면 반환 값의 prvalue가 개체를 직접 초기화합니다 t. "임시 생성 및 복사 / 이동"단계는 없습니다. 이후 Func()의 반환 값에 prvalue 동등하다 T(), t직접 초기화됩니다 T()당신이했던 정확히 것처럼,T t = T() .

prvalue가 다른 방식으로 사용되면 prvalue는 해당 표현식에서 사용되는 임시 객체를 구체화합니다 (또는 표현식이없는 경우 삭제됨). 그래서 만약 당신이했다면 const T &rt = Func();, prvalue는 일반적인 임시 수명 연장 항목과 함께 T()참조가에 저장 될 임시 ( 초기화 자로 사용) 를 구체화합니다 rt.

제거가 보장되는 한 가지는 움직이지 않는 객체를 반환하는 것입니다. 예를 들면lock_guard 복사하거나 이동할 수 없으므로 값으로 반환하는 함수를 가질 수 없습니다. 그러나 보장 된 복사 제거 기능을 사용하면 가능합니다.

보장 된 제거는 직접 초기화에서도 작동합니다.

new T(FactoryFunction());

경우 FactoryFunction반환 T값에 의해,이 표현은 할당 된 메모리에 반환 값을 복사하지 않습니다. 대신 메모리를 할당 하고 할당 된 메모리 를 함수 호출에 대한 반환 값 메모리로 사용합니다.

따라서 값으로 반환하는 팩토리 함수는 알지 못해도 힙 할당 메모리를 직접 초기화 할 수 있습니다. 물론 이러한 기능이 내부적 으로 보장 된 복사 제거 규칙을 따르는 한. 그들은 유형의 prvalue를 반환해야합니다 T.

물론 이것도 작동합니다.

new auto(FactoryFunction());

유형 이름 작성을 좋아하지 않는 경우.


위의 보장은 prvalue에 대해서만 작동한다는 것을 인식하는 것이 중요합니다. 즉, 명명 된 변수를 반환 할 때 보장 할 수 없습니다 .

T Func()
{
   T t = ...;
   ...
   return t;
}

이 경우 t에도 액세스 가능한 복사 / 이동 생성자가 있어야합니다. 예, 컴파일러는 복사 / 이동을 최적화하도록 선택할 수 있습니다. 그러나 컴파일러는 여전히 액세스 가능한 복사 / 이동 생성자의 존재를 확인해야합니다.

따라서 NRVO (Named Return Value Optimization)에 대한 변경 사항은 없습니다.


1
@BenVoigt : 사소하게 복사 할 수없는 사용자 정의 유형을 레지스터에 넣는 것은 제거가 사용 가능한지 여부에 관계없이 ABI가 할 수있는 실행 가능한 일이 아닙니다.
Nicol Bolas

1
이제 규칙이 공개되었으므로 "prvalues ​​is initializes"개념으로이를 업데이트하는 것이 좋습니다.
Johannes Schaub-litb

7
@ JohannesSchaub-litb : C ++ 표준의 세부 사항에 대해 너무 많이 알고있는 경우에만 "모호함"입니다. C ++ 커뮤니티의 99 %는 "보장 된 복사 제거"가 무엇을 의미하는지 알고 있습니다. 이 기능을 제안하는 실제 논문의 제목은 "복사 보장 제거"입니다. "단순화 된 값 범주를 통해"추가하면 사용자가 혼란스럽고 이해하기 어렵습니다. 또한 이러한 규칙은 값 범주에 대한 규칙을 실제로 "단순화"하지 않기 때문에 잘못된 이름입니다. 원하든 원하지 않든 "보장 된 복사 제거"라는 용어는이 기능을 의미하며 다른 것은 없습니다.
Nicol Bolas

1
나는 prvalue를 집어 들고 가지고 다닐 수 있기를 원합니다. 나는 이것이 std::function<T()>정말로 ( 원샷)이라고 생각합니다.
Yakk-Adam Nevraumont 2017 년

1
@LukasSalich : C ++ 11 질문입니다. 이 답변은 C ++ 17 기능에 대한 것입니다.
Nicol Bolas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.