위와 같이 정의가 가능하다.
좌측값은 어떠한 메모리 위치를 가리키는데, & 연산자를 통해 그 위치를 참조할 수 있다. 우측값은 좌측값이 아닌 값들이다
#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가 바인딩 가능하다.