Modern C++ - 우측값 참조이동 의미론

진경천·2024년 3월 19일
0

C++

목록 보기
85/90

우측값과 좌측값

  • 좌측값(L value) : 어떠한 메모리 위치를 가리키는데, & 연산자를 통해 그위치를 참조할 수 있다.
  • 우측값(R value) : 좌측값이 아닌 값들

위와 같이 정의가 가능하다.
좌측값은 어떠한 메모리 위치를 가리키는데, & 연산자를 통해 그 위치를 참조할 수 있다. 우측값은 좌측값이 아닌 값들이다

move 연산

#pragma warning(disable : 4996)
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const char* wrap(const char* str) {
	return str ? str : "";
}

class String {
private:
	char* _str;

public:
	String() { _str = nullptr; }
	explicit String(const char* str)
		: _str(new char[strlen(str) + 1]) {
		strcpy(_str, str);
		cout << "Construct : " << _str << endl;
	}

	String(const String& other)
		: _str(new char[strlen(other._str) + 1]) {
		strcpy(_str, other._str);
		cout << "Copy Construct : " << _str << endl;
	}

	String(String&& other) noexcept
		: _str(other._str) {
		cout << "Move Construct : " << wrap(_str) << endl;
		other._str = nullptr;
	}

	~String() {
		cout << "Destruct : " << wrap(_str) << endl;
		delete[] _str;
	}

	String& operator=(const String& other) {
		cout << "Copy operator : " << _str << " = " << other._str << endl;
		delete[] _str;

		_str = new char[strlen(other._str) + 1];
		strcpy(_str, other._str);

		return *this;
	}

	String& operator=(String&& other) noexcept {
		cout << "Move operator : " << wrap(_str) << " = " << other._str << endl;

		delete[] _str;
		_str = other._str;
		other._str = nullptr;

		return *this;
	}

	friend std::ostream& operator<<(std::ostream & os, String & str) {
		return (os << str._str);
	}
};

template<typename T>
Swap(T& x, T& y){
	T temp = x;
    x = y;
    y = temp;
}

int main() {
	String s0("abc");
    String s1("def");
    Swap(s0, s1);

위와 같이 cstring 헤더를 이용해 String 클래스와 Swap 함수를 구현했다.

실행 결과

Construct : abc
Construct : def
Copy Construct : abc
Copy operator : abc = def
Copy operator : def = abc
Destruct : abc
Destruct : abc
Destruct : def

Swap이 발생할 때 복사연산대입을 하여 쓸모없는 abc객체가 생긴 것을 볼 수 있다.

여기서 아래와 같은 Move 연산을 추가하게 되면

String(String&& other) noexcept
	: _str(other._str) {
	cout << "Move Construct : " << wrap(_str) << endl;
	other._str = nullptr;
}

String& operator=(String&& other) noexcept {
	cout << "Move operator : " << wrap(_str) << " = " << other._str << endl;

	delete[] _str;
	_str = other._str;
	other._str = nullptr;

	return *this;
}

실행결과

Construct : abc
Construct : def
Move Construct : abc
Move operator : = def
Move operator : = abc
Destruct :
Destruct : abc
Destruct : def

위와 같이 Move 연산이 실행된다.

template<typename T>
void Swap(T& x, T& y) {
	T temp = std::move(x);
	x = std::move(y);
	y = std::move(temp);
}

하지만 이때 Swap함수를 위와 같이 변수에 std::move를 넣어주어야 한다.

컴파일을 할 때 x, y, temp를 나중에 쓰인다고 판단하여 복사대입연산을 실행하기 때문에 std::move를 이용하여 강제로 이동생성자를 호출해야한다.

또한 move 연산자와 생성자에는 noexcept를 붙여 예외에 의해 종료되지 않도록 지정해야 한다.

우측값 참조

임의의 타입 T에 대해 T&&를 우측값 참조라고 정의된다.
기존의 레퍼런스인 T&는 좌측값 참조라고 정의된다.

int num0 = 10;	// 임시값
int& num1 = num0;	// lValue reference
int&& num2 = 10;	// rValue reference 임시값이 들어가면 error

우측값 참조와 좌측값 참조

int num0 = 10;	// 임시값 10
int& num1 = num0;
int&& num2 = num0 + num1;

int& num3 = 3;	// error : const가 아닌 lValue의 초기값은 lValue여아한다.
int&& num4 = num0;	// error : rValue에 lValue 바인딩 불가
int&& num5 = num1;

int& num6 = num2;
int&& num7 = num2;	// error : rValue에 lValue 바인딩 불가
int&& num8 = std::move(num0);

rValue에는 lValue가 바인딩이 불가하다.
const가 아닌 lValue의 초기값은 lValue여아한다.
std::move()를 이용하면 rValue에 lValue가 바인딩 가능하다.

profile
어중이떠중이

0개의 댓글