ShovelingLife
A Game Programmer
ShovelingLife
전체 방문자
오늘
어제
  • 분류 전체보기 (1074)
    • 그래픽스 (57)
      • 공통 (19)
      • 수학 물리 (22)
      • OpenGL & Vulkan (1)
      • DirectX (14)
    • 게임엔진 (183)
      • Unreal (69)
      • Unity (103)
      • Cocos2D-X (3)
      • 개인 플젝 (8)
    • 코딩테스트 (221)
      • 공통 (7)
      • 프로그래머스 (22)
      • 백준 (162)
      • LeetCode (19)
      • HackerRank (2)
      • 코딩테스트 알고리즘 (8)
    • CS (235)
      • 공통 (21)
      • 네트워크 (44)
      • OS & 하드웨어 (55)
      • 자료구조 & 알고리즘 (98)
      • 디자인패턴 (6)
      • UML (4)
      • 데이터베이스 (7)
    • 프로그래밍 언어 (349)
      • C++ (168)
      • C# (90)
      • Java (9)
      • Python (33)
      • SQL (30)
      • JavaScript (8)
      • React (7)
    • 그 외 (10)
      • Math (5)
      • 일상 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • Source Code 좌측 상단에 복사 버튼 추가 완료
  • 언리얼 엔진 C++ 빌드시간 단축 꿀팁
  • 게임 업계 코딩테스트 관련
  • 1인칭 시점으로 써내려가는 글들

인기 글

태그

  • 백준
  • 포인터
  • C++
  • c#
  • 클래스
  • C
  • 파이썬
  • 티스토리챌린지
  • 프로그래머스
  • 언리얼
  • 오블완
  • 유니티
  • SQL
  • 알고리즘
  • 함수
  • 그래픽스
  • 문자열
  • string
  • 배열
  • Unity

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ShovelingLife

A Game Programmer

프로그래밍 언어/C++

C++ RVO NRVO 반환값 최적화 / Copy Elision(복사 생략)

2022. 6. 14. 14:00

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();
}
 
2) Passing Temporary as Value
Copy Elision의 두번째 예는 함수 인자로써 임시객체를 값으로 전달하는 것이다.
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

C++17에서 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&&)" 을(를) 참조할 수 없습니다. 삭제된 함수입니다.
분명 Copy Elision의 형식을 따르고 있음에도 12라인과 17라인에 대하여, 함수 참조를 찾을 수 없다고 에러가 발생한다. (참고로, 2017/07/15까지는 GCC7에서만 컴파일이 가능하다)
C++17에서는 Non-movable 객체에 대해서도 Copy Elision이 정상 동작함을 보장하는 것이다. 이것이 C++17의 "Guaranteed Copy Elision"의 핵심적인 내용이다.
그런데 위 예제에 RVO에 대해서만 코드가 작성되어 있고, NRVO에 대해서는 코드가 작성되어 있지 않다. C++17에서 NRVO에 대해서는 Guaranteed Copy Elision이 적용되지 않았기 때문이다.

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
    '프로그래밍 언어/C++' 카테고리의 다른 글
    • C++ RTTI 그리고 vtable(가상 함수 테이블)
    • C++ 출력(std::format)과 for-range loop 꿀팁
    • C++ 가상함수 테이블 (Virtual Table)
    • C++ 캐스팅 static_cast / reinterpret_cast 예제
    ShovelingLife
    ShovelingLife
    Main skill stack => Unity C# / Unreal C++ Studying Front / BackEnd, Java Python

    티스토리툴바