Copy Elision
Copy Elision(복사 제거)란 C++11에서 공식화된 기능으로 cppreference의 설명을 참고하면 Omits copy and move constructor, resulting zero-copy pass-by-value semantics 즉 컴파일러가 복사 또는 이동 연산자를 회피 할 수 있으면 회피하는 것을 허용하는 방식 그래서 특정 조건을 만족하면 컴파일러가 임의로 최적화를 위해 복사 및 이동 연산을 생략함. 이게 생각보다 엄청 큰 효과를 가져온다고 하고 크게 두 가지 경우가 있는데 Return Value Optimization(반환값 최적화)인 경우와 class type의 template object(임시 개체)가 동일한 유형의 복사 될 때인 경우다.
Return Value Optimization
아래의 코드와 같은 경우 컴파일러는 개체의 복사 연산자를 파기한다 사실 코드만 보면 복사 연산자는 무조건 일어나야 하지만 Copy Elision 최적화로 인해 컴파일러가 임시 복사본을 만드는 코드를 직접 제어해서 복사 및 이동 연산자를 사용하지 않아 임시로 초기화 식을 가져와서 함수의 반환 값을 직접 초기화 한다.
1. Copy Elision
1) Return Value Optimization / Named Return Value Optimization
#include <iostream>
using namespace std;
struct Foo
{
Foo()
{
std::cout << "Constructed\n";
}
Foo(const Foo&)
{
std::cout << "Copy-constructed\n";
}
Foo(Foo&&) noexcept
{
std::cout << "Move-constructed\n";
}
~Foo()
{
std::cout << "Destructed\n";
}
};
// Return Value Optimization
Foo RVO_F()
{
return Foo();
}
// Named Return Value Optimization
Foo NRVO_F()
{
Foo foo;
return foo;
}
int main()
{
// 아래 대입문들에서 복사생성 생략
// "Constructed"
// "Destructed"
Foo rvo_foo = RVO_F();
cout << endl;
// "Constructed"
// "Destructed"
Foo nrvo_foo = NRVO_F();
}
만약, RVO 최적화가 없을 경우엔 다음과 같이 복사 생성자가 2번 추가로 호출되는 것을 확인할 수 있을 것이다.
// "Constructed" - 로컬에서 객체 생성
// "Move-Constructed" - 함수 반환값 임시객체로의 이동
// "Destructed" - 로컬 파괴
// "Move-Constructed" - main의 rvo_foo로의 이동
// "Destructed" - 반환값 임시객체 파괴
// "Destructed" - rvo_foo 파괴
int main()
{
Foo rvo_foo = RVO_F();
}
void f(Foo f)
{
std::cout << "Fn\n";
}
int main()
{
// without Copy Elision
/*
Constructed
Move-Construected
Fn
Destructed
Destructed
*/
f(Foo());
// with Copy Elision
/*
Constructed
Fn
Destructed
*/
f(Foo());
}
2. Guaranteed Copy Elision
- 이동이 불가능한 타입에 대해서는 Copy Elision이 적용되지 않았음.
- 결과적으로, 내용상 Copy Elision이 가능함에도 Non-movable에 대해서 컴파일 에러 발생하는 문제가 있었음.
struct Foo
{
Foo() { std::cout << "Constructed\n"; }
Foo(const Foo &) = delete;
Foo(const Foo &&) = delete;
~Foo() { std::cout << "Destructed\n"; }
};
Foo f()
{
return Foo();
}
int main()
{
Foo foo = f();
}
// E1776 : 함수 "Foo::Foo(Const Foo&&)" 을(를) 참조할 수 없습니다. 삭제된 함수입니다.
// E1776 : 함수 "Foo::Foo(Const Foo&&)" 을(를) 참조할 수 없습니다. 삭제된 함수입니다.
3. Value Categories
"Guaranteed Copy Elision"이 C++ 표준 위원회에 제출될 당시의 전체 이름은 "Guaranteed copy elision through simplied value categories" 이다.
Guaranteed Copy Elision을 달성하기 위해, 제안서에는 다음의 내용이 제안되어 있다. prvalue(pure rvalue)와 그것들에 의해 초기화된 임시 객체를 구분해야 한다. 그리고, prvalue에 대해서만 Guaranteed Copy Elision을 보장한다.
보다 구체적으로 이야기하면, prvalue는 객체의 initializer로 정의되고, glvalue(generalized lvalue)는 객체의 위치로 정의된다. 만약, prvalue가 같은 타입의 객체의 initializer로 사용되면 직접 초기화된다. 결과적으로 함수의 반환값을 임시로 초기화하면, 값에 대해 복사/이동하는 것이 아니라 직접 초기화한다는 것이다. 즉, 객체의 복사/이동 생성자에 더 이상 접근할 필요가 없다.
NRVO의 경우 named 값은 prvalue가 아닌, glvalue이기에, C++17의 Guaranteed Copy Elision의 대상에서 제외된 것이다.
NRVO 역시 최적화에 중요한 요소이긴 하지만, 다음 C++ 표준에서나 기대할 수 있을 듯 하다.
출처 : https://hodongman.github.io/2020/11/02/C++-copy-Elision-copy.html
출처 : http://sweeper.egloos.com/3204454#comment_3204454
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ RTTI 그리고 vtable(가상 함수 테이블) (0) | 2022.06.14 |
---|---|
C++ 출력(std::format)과 for-range loop 꿀팁 (0) | 2022.06.14 |
C++ 가상함수 테이블 (Virtual Table) (0) | 2022.06.13 |
C++ 캐스팅 static_cast / reinterpret_cast 예제 (0) | 2022.06.10 |
C++ SFINAE 여러 타입에 대응하는 템플릿 오버로딩 (0) | 2022.06.10 |