c++ 11에서 부터 지원되는 이동 연산자에 대해서 알아 봅시다. 

우선 이동 연산자가 왜 필요하게 되었는지에 대해서 보기전에 lvalues와 rvalues에 대해서 알아 봅시다.

Lvalues 와 Rvalues

 msdn에 설명되어 있는 Lvalues와 Rvalues를 참조해서 설명드립니다. 

c++ 17 표현식

c++의 모든 표현은 타입을 가지며 값 카테고리에 속합니다.

값 카테고리는 컴파일러가 생성, 복사, 이동 임시 객체들을 표현할 때 따라야하는 규칙을 기반으로 만들어집니다. 

lvalue

lvalue는 접근할 수 있는 주소를 가진 변수를 말합니다. 이동 연산을 할 수 없습니다.

  • 이름이 있는 변수, 함수
    • int a;
    • &foo()
  • 선행 증감 연산자
    • ++a, --b
  • lvalue 배열의 인덱스 접근
    • l[n]
  • string 리터럴
    • "hello move sementics"
  • 등등

prvalue 

 prvalue는 접근은 할 수 있지만 주소를 가지지 못한 표현식을 말합니다. 이동 연산이 가능합니다.

 pure rvalue라고도 합니다.

  • 리터럴 값
    • 1, 1.3f
    • sting 리터럴 값은 제외
  • 후행 증감 연산자
    • a++, b--
  • 값 리턴 함수 호출
    • return str1 + str2; 리턴 타입이 값인 함수
  • 비 참조 캐스팅 ( static_cast<double>(x), (int) 42 같은)
  • 등등 

xvalue

 xvalue는 접근 할 수 있는 주소를 가지지만 이동 연산을 할 수 있습니다.

이동 연산 후에는 객체의 안정성을 보장 하지 않습니다.

  • rvalue 참조를 리턴하는 함수
    • std::move(x)
  • rvalue 배열의 인덱스 접근
    • r[n]
  • rvalue 참조 캐스팅 (  같은)
    • static_cast<int&&>(i)
  • 등등 

 

rvalue

 prvalue + xvalue를 묶어서 rvalue라고 합니다. 

glvalue

 lvalue + xvalue를 묶어서 glvalue라고 합니다.

예 제 

int tmain(int argc, _TCHAR* argv[])
{
    int num1 = 5, num2 = 3;
    
    num1 = 10;                  // num1은 lvalue, 10은 int형 prvalue
    num1 = num2;                // num1은 lvalue, num2은 lvalue
    int num3 = num1 + num2;     // num3은 lvalue, num1 + num2은 prvalue
    
    std::move(num3);            // rvalue 참조를 리턴 xvalue
    static_cast<DWORD>(num1)    // 값으로 캐스팅 lvalue
    static_cast<DWORD&&>(num1)  // rvalue 참조로 캐스팅 xvalue
}

 간단하게 rvalue와 lvalue 구분하려면 접근 할 수 있는 주소가 있는지 확인 합니다.

 주소가 없다면 rvalue라고 할 수 있고 만약에 주소가 있다면 rvalue 참조타입으로 캐스팅 되어있는지 확인 합니다.

 rvalue 참조로 캐스팅 되었다면 xvalue라고 할 수 있고 아니라면 lvalue에 속합니다. 

https://docs.microsoft.com/ko-kr/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=vs-2019

 

값 종류: Lvalue 및 Rvalue (C++)

Lvalue 및 Rvalue (C++)Lvalues and Rvalues (C++) 이 문서의 내용 --> 모든 C++ 식에는 형식이 고 속한를 값 범주가합니다.Every C++ expression has a type, and belongs to a value category. 값 범주에는 컴파일러는 만들기, 복사 및 식 평가 중에 임시 개체를 이동 하는 경우 따라야 하는 규칙에 대 한 기반이 됩니다.The value categories are the b

docs.microsoft.com

https://en.cppreference.com/w/cpp/language/value_category

 

Rvalue 참조 선언 자:&&

Rvalue 참조 선언 자:&&Rvalue Reference Declarator: && 이 문서의 내용 --> rvalue 식에 대한 참조를 보유합니다.Holds a reference to an rvalue expression. 구문Syntax type-id && cast-expression rvalue 참조를 사용하면 lvalue와 rvalue를 구별할 수 있습니다.Rvalue references enable you to distinguish an lv

docs.microsoft.com

Move semantics

 c++11에서는 lvalue와 rvalue를 구분한 파라미터를 전달받아 처리 할 수 있습니다. 

 이렇게 지원을 하게 된 이유는 효율성을 증가시키기 위함입니다. 

 그럼 어떻게 효율성을 증가 시킬 수 있을까요? 예제를 통해서 설명 드리겠습니다. 

using namespace std;


struct Person
{
    string name;
    int* year = nullptr;

    Person()
    {}

    Person(string p_name, const int p_year) : name(p_name), year(nullptr)
    {
    	year = new int(p_year);
        cout << "constructed" << endl;
    }

    Person(const Person& other) noexcept :
        name(other.name), year(nullptr)
    {
    	year = new int(p_year);
        cout << "copy constructed" << endl;
    }

    Person(Person&& other) noexcept :
        name(move(other.name)), year(nullptr) 
    {
        // Rvalue의 힙에서 할당된 year를 이동시킵니다.
        year = other.year;
        // Rvalue의 year를 nullptr 초기화 시킵니다.
        other.year = nullptr;

        cout << "move constructed" << endl;
    }

    Person& operator=(const Person& other) noexcept
    {
        if (this != &other)
        {
            this->name = other.name;
            this->year = new int(*other.year);
        }
        cout << "copy Assignment operator" << endl;
        return *this;
    }
    
    Person& operator=(Person&& other) noexcept
    {
        if (this != &other)
        {
            this->name = std::move(other.name);

            if (this->year) delete this->year;

            this->year = other.year;
            other.year = nullptr;
        }
        cout << "move Assignment operator" << endl;
        return *this;
    }
    
    virtual ~Person()
    {
        if (nullptr != year)
        {
            delete year;
        }
        cout << "destructed " << endl;
    }
};

int main()
{
    vector<Person> Container;
    cout << "call push_back(Person(\"Ahn\", 1985))" << endl;
    Container.push_back(Person("Ahn", 1985));
    return 0;
}

복사 연산 수행시 메모리 구조

 

이동 연산 수행시 메모리 구조

 main 함수의 3라인을 보면 vector의 push_back값으로 "Person("Ahn", 1985)" Rvalue를 넘기고 있습니다.

 이렇게 호출을 하면 Person의 Person(Person && other) 이동 연산자가 호출되게 됩니다. 

 Rvalue의 경우 앞서 말한대로 해당 실행라인이 지나면 무효화 되는 임시객체라고 설명을 드렸는데 

 임시 객체에 대해서 깊은 복사를 하는 것은 비효율적으로 보입니다.

 어짜피 없어질 객체이니 얕은 복사를 통해서 데이터를 이동시키면 효율적으로 작동 할 수 있습니다.

 msdn의 이동 생성자 및 이동 할당 연산자 정의 방법입니다. 

https://docs.microsoft.com/ko-kr/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=vs-2019

 

방법: 정의 이동 생성자 및 이동 할당 연산자 (C++)

이동 생성자 및 이동 할당 연산자(C++)Move Constructors and Move Assignment Operators (C++) 이 문서의 내용 --> 이 항목에서는 작성 하는 방법에 설명 합니다는 이동 생성자 와 이동 할당 연산자에 대 한를 C++ 클래스.This topic describes how to write a move constructor and a move assignment operator for a C++ class. 이동 생성자

docs.microsoft.com

 

'c++ > modern c++' 카테고리의 다른 글

[c++] auto와 decltype 키워드  (0) 2020.03.15
[c++] std::move와 std::forward  (4) 2020.03.07
[c++] 람다(Lambda) 표현식  (0) 2020.03.04
[c++] weak_ptr  (1) 2020.03.02
[c++] shared_ptr  (0) 2020.02.29

+ Recent posts