오른값 참조 등장 이후 모던 C++이 기존 C++보다 더 빠르게 작동하게 되었다.

 

1. 왼값(lvalue) vs 오른값(rvalue)

 

(1) 정의

- 왼값(Left Value) : 단일식을 넘어 계속 지속되는 개체, (C언어에서  ; 대입식에서 왼쪽에 오는 것)

int a = 3;
int b = 2; // 왼값 : a, b

- 오른값(Right Value) : 왼값이 아닌 나머지 (임시 값, 열거형, 람다, i++ ...), (C언어에서  ; 대입식에서 오른쪽에 오는 것)

int c = a * b; // 오른값 : a * b

 

(2) 왼쪽값 참조(Lvalue Reference)

class Knight
{
public:

    void PrintInfo()
    {

    }

public:

    int _hp = 100;

};

void TestKnight_Copy(Knight knight){}
void TestKnight_LValueRef(Knight& knight){}
void TestKnight_ConstLValueRef(const Knight& knight){}

int main()
{
    Knight k1;
    
    TestKnight_Copy(k1);

    TestKnight_LValueRef(k1);
    TestKnight_ConstLValueRef(k1);
}

임의의 클래스 knight를 만들고 이를 이용하는 여러 함수를 만들어본다.

기존의 왼값 참조인 TestKnight_LValueRef는 Knight 타입의 매개 변수를 받는다.

이 때 오른값의 직접적인 참조가 불가능하다.

    TestKnight_LValueRef(k1);
    TestKnight_LValueRef(Knight());

즉 두번째에서 Knight의 기본 생성자를 이용한 임시 객체를 참조할 수 없다.

하지만 const가 붙은 참조를 하면 임시 객체를 참조할 수 있다.

void TestKnight_ConstLValueRef(const Knight& knight){}

int main()
{ //~~
	TestKnight_ConstLValueRef(k1);
}

단, const의 특성으로 인해서 값을 변경하는 것이 불가능(읽기 전용)하다.

또한 멤버 함수를 사용할 수도 없다(const가 붙으면 가능함).

 

(2) 오른값 참조(Rvalue Reference)

void TestKnight_RValueRef(Knight&& knight){}
    TestKnight_RValueRef(Knight());

오른값 참조에서는 임시로 생성된 객체에 대한 접근이 가능해진다.

 

단, 오른값 참조로 선언하면 기존의 왼값 참조는 따로 구현해야한다.

    TestKnight_RValueRef(k1); // 오류 발생

 

(3) 왼쪽값 참조 형 변환하기

오른값 참조에서는 임시 객체에 대한 접근만 허용하는 것 뿐만이 아니다.

    TestKnight_RValueRef(static_cast<Knight&&>(k1));

이렇게 기존의 객체를 형 변환을 통해서 오른값 참조를 사용할 수 있다.

 

2. 오른값 참조의 특징

(1) 빠른 속도

class Pet
{


};

class Knight
{
public:

    Knight () // 기본 생성자
    {

    }

    Knight(const Knight& knight) // 복사 생성자
    {

    }

    ~Knight()
    {
        if (_pet)
            delete _pet;
    }

    void operator=(const Knight& knight) // 복사 대입 연산자
    {
        // 깊은 복사
        _hp = knight._hp;

        if (knight._pet)
            _pet = new Pet(*knight._pet);
    }

public:

    int _hp = 100;
    Pet* _pet = nullptr;
};

위와 같이 Pet 클래스를 만들고 각 Knight 객체가 pet을 들게 한다.

이렇게 설정하기 위해서는 Pet 클래스의 pet 객체를 만들고 그것을 knight 객체가 들고있게 해야한다.

그렇게 되면 각 Knight의 객체들이 서로 다른 pet을 가지게 된다.

 

이런 코드는 추후에 코드가 방대해지고 여러 사양이 추가되면, Knight 객체 하나를 생성하는데 큰 리소스를 차지하게 된다.

 

이것을 방지하고 코드를 더 빠르게 작동하게 하기 위해 임시 객체를 수정하는 오른값 참조가 등장하게 되었다.

    void operator=(Knight&& knight) // 이동 대입 연산자
    {
        // 얕은 복사
        _hp = knight._hp;
        _pet = knight._pet;

        knight._pet = nullptr;
    }

knight의 임시 객체를 만들어 그 임시 객체가 가지고 있는 pet의 포인터를 그대로 가져오게하는 방식으로 작동한다.

    Knight k2;
    k2._pet = new Pet();
    k2._hp = 1000;


    Knight k3;
    k3 = static_cast<Knight&&>(k2);

k2는 임시 객체이고 k3는 k2의 정보를 그대로 이동시켜서 사용한다.

따라서 객체를 전체적으로 복사하는 것 보다 임시 객체의 정보를 이동시키는 것이 더 빠르게 작동할 수 있다.

 

(2) move 문법

참고로 static_cast 대신 move를 이용하여 캐스팅할 수 있다.

k3 = move(k2);
// 본래 이름 후보 중 하나가 rvalue_cast 였다고 한다..

 

'기초 C++ 스터디 > 모던 C++' 카테고리의 다른 글

10-9. 람다(lambda) 표현식  (0) 2023.06.22
10-8. 전달 참조 (forwarding reference)  (0) 2023.06.22
10-6. override, final  (0) 2023.06.20
10-5. delete - 삭제된 함수  (0) 2023.06.20
10-4. enum class  (0) 2023.06.20