Skip to main content

[C++] 우측값과 이동 시맨틱



우측값이란? #

이름 설명
좌측값
(l-value)
단일 표현식 이후에도 없어지지 않고 지속되는 객체이다.
이름을 가지는 객체라고 볼 수 있다.
읽기, 쓰기가 가능하다.
우측값
(r-value)
표현식이 종료된 이후에는 더이상 존재하지 않는 임시적인 값이다.
읽기만 가능하다.
// 왼쪽에 있는 것들은 모두 l-value이며, 
// 오른쪽에 있는 것들은 모두 r-value이다. 
int num = 3;
int* ptr = #
int sum = num + 3;
int res = sqrt(3);

++x; // 이것은 자기 자신을 리턴하므로 l-value이다. 
x++; // 이것은 증가된 복사본을 리턴하므로 r-value이다. 



우측값을 참조하는 방법 #

  • 좌측값은 &를 사용해서 참조한다.
  • 우측값은 &&를 사용해서 참조한다.
// l-value 참조 방법
int num = 3;
int& lref = num;
lref = 4;

// r-value 참조 방법
int&& rref =3;
rref = 4;       // 별명을 붙이니 l-value가 되어, 수정이 가능해졌다!

  • const 를 붙이면 우측값도 &로 참조할 수 있다.
    • 왜냐하면 const 를 붙이면 읽기만 가능해지기 때문이다.
const int& ref = 3;  // OK



이동 시맨틱(Move Semantics) #

  • 비용이 비싼 깊은 복사를 하는 대신에 주소값만 가로채서 소유권을 이동시키는 것(얕은 복사)이다.
  • 복사 생성자, 대입 연산자에 우측값 참조를 사용해서 이동 생성자, 이동 대입 연산자로 구현된다.
#include <iostream>

class MyClass
{
private:
    int n;    // 매개변수 수
    char* pc; // 데이터를 가리키는 포인터

public:    
    MyClass(int num) : n(num)
    {
        cout << "일반 생성자" << endl;
        
        pc = new char[n];
    }

    ~MyClass()
    {
        cout << "소멸자" << endl;
		
        delete[] pc;
    }
    
    MyClass(const MyClass& f) : n(f.n)
    {
        cout << "복사 생성자" << endl;
        
        // 새로운 복사본이 만들어진다. (깊은 복사)
        pc = new char[n];
        for (int i = 0; i < n; i++)
            pc[i] = f.pc[i];
    }

    MyClass(MyClass&& f) noexcept : n(f.n)
    {
        cout << "이동 생성자" << endl;
        
        pc = f.pc;       // 주소 가로채기
        f.pc = nullptr;  // 이전 객체가 아무것도 반환하지 않도록 함
    }
	
    MyClass& operator=(const MyClass& f)
    {
        cout << "복사 대입 연산자" << endl;
        
        if (this == &f) return *this;
        delete[] pc;
        n = f.n;
        
        // 새로운 복사본이 만들어진다. (깊은 복사)
        pc = new char[n];
        for (int i = 0; i < n; i++)
            pc[i] = f.pc[i];
        	
        return *this;
    }
	
    MyClass& operator=(MyClass&& f) noexcept
    {
        cout << "이동 대입 연산자" << endl;
        
        if (this == &f) return *this;
        delete[] pc;
        n = f.n;
        
        pc = f.pc;      // 주소 가로채기
        f.pc = nullptr; // 이전 객체가 아무것도 반환하지 않도록 함
        f.n = 0;
        
        return *this;
    }

    MyClass operator+(const MyClass& f) const
    {
        MyClass temp = MyClass(n + f.n);
        
        for (int i = 0; i < n; i++)
            temp.pc[i] = pc[i];
        
        for (int i = n; i < temp.n; i++)
            temp.pc[i] = f.pc[i - n];
        
        return temp;
    }
};

int main()
{
    MyClass one(1);   // 일반 생성자 호출 
    MyClass two(one); // one은 l-value이므로 복사 생성자 호출
    
    MyClass three = one + two; // one + two는 r-value이므로 이동 생성자 호출 
}

  • 좌측값도 이동 시맨틱을 하려면?
    • move()를 사용하면 된다.
// move로 l-value가 r-value가 되어 이동 생성자 호출  
MyClass four(std::move(one)); 



References #