[C++] Rvalue Reference
Rvalue reference에 대해 말하기 위해선 Rvalue, Lvalue에 대해서 알아야 한다.
Rvalue, Lvalue
Rvalue, lvalue라는 용어는 general한 용어로 =
기호를 기준으로 말하면 이해하기 쉽다.
Lvalue = Rvalue
Lvalue는 어떤 변수에 저장되어 지속되는 값을 의미하며, Rvalue는 이런 지속적인 값을 가지지 않는 임시의 값을 나타낸다.
Lvalue
#include <iostream> int main() { int a = 777; int &b = a; std::cout << b << std::endl; return 0; }
위 코드는 777
이라는 값이 출력 될 것이다. int &b = a
를 통해 a
의 주소를 b
가 가지고 있기 때문에 a
의 값에 저장된게 b
에 저장된 a
주소를 통해 접근 가능하다.
Rvalue
#include <iostream> int main() { int a = 777; // int &lval = 10; // Error int &lval = a; // int &&rval = a; // Error int &&rval = 10; std::cout << lval << std::endl; std::cout << rval << std::endl; return 0; }
위에 보면 int &
타입의 경우는 내부에 또 다른 주소값을 통해 참조하는 타입이며, rvalue는 int &&
와 같이 연산자를 나타낼 수 있는데 이는 불필요한 복사를 제거하기 위해서 제안된 기술이다.
Rvalue Reference
#include <iostream> #include <cstring> #include <vector> using namespace std; class A { public: A (int _size): size(_size), data(new int[_size]) { cout << "Normal constructor (" << _size << ")"<< endl; } A (const A& _rhs): size(_rhs.size), data(new int[_rhs.size]) { cout << "Copy constructor (" << _rhs.size << ")"<< endl; memcpy(data, _rhs.data, _rhs.size); } A& operator=(const A& _rhs) { if (this != &_rhs) { cout << "Copy assignment operator (" << _rhs.size << ")" << endl; delete[] data; size = _rhs.size; data = new int[size]; memcpy(data, _rhs.data, _rhs.size); } return *this; } ~A() { cout << "Destructor (" << size << ")" << endl; if (data != NULL) { cout << "[Delete resources]" << endl; delete[] data; } } private: int *data; int size; }; int main() { vector<A> list; cout << "######### 1st ########" << endl; list.push_back(A(1)); cout << "######### 2nd ########" << endl; list.push_back(A(2)); cout << "######### 3rd ########" << endl; list[0] = A(3); cout << "######### Done ########" << endl; return 0; }
########## 1st ########## Normal constructor (1) Copy constructor (1) Destructor (1) [Delete resources] ########## 2nd ########## Normal constructor (2) Copy constructor (2) Copy constructor (1) Destructor (1) [Delete resources] Destructor (2) [Delete resources] ########## 3rd ########## Normal constructor (3) Copy assignment operator (3) Destructor (3) [Delete resources] ########## Done ########## Destructor (3) [Delete resources] Destructor (2) [Delete resources]
위 코드를 보면 vector 내부에 object 2개를 push_back
함수를 통해 넣는다. vector 구조 특성상 capacity는 size의 2배를 유지해야하기 때문에 size가 capacity의 1/2보다 커지게 되면 새로운 메모리를 할당하는 동작이 이뤄진다. 이때 불필요하게 memory allocation & free 동작이 이뤄질 수 있는데 data size에 따라서 성능에 큰 영향을 미칠 수 있다.
위 결과를 보면 copy constructor가 수행될 때마다 새로운 메모리를 할당받고 destruction 된다. [Delete resources]
라는 message가 표기되는 것을 통해 매번 memory free 동작이 일어난다. 이런 새로운 memory allocation & free 을 단순한 move 변경 하기 위해서 move construction이 사용된다.
Memory allocation 시점
A(1)
list.push_back(A(1));
A(2);
list.push_back(A(2));
A(3);
#include <iostream> #include <cstring> #include <vector> using namespace std; class A { public: A (int _size): size(_size), data(new int[_size]) { cout << "Normal constructor (" << _size << ")"<< endl; } A (const A& _rhs): size(_rhs.size), data(new int[_rhs.size]) { cout << "Copy constructor (" << _rhs.size << ")"<< endl; memcpy(data, _rhs.data, _rhs.size); } A (A&& _rhs): data(NULL), size(0) { cout << "Move constructor (" << _rhs.size << ")" << endl; data = _rhs.data; size = _rhs.size; _rhs.data = NULL; } A& operator=(const A& _rhs) { if (this != &_rhs) { cout << "Copy assignment operator (" << _rhs.size << ")" << endl; delete[] data; size = _rhs.size; data = new int[size]; memcpy(data, _rhs.data, _rhs.size); } return *this; } A& operator=(A&& _rhs) { if (this != &_rhs) { cout << "Move assignment operator (" << _rhs.size << ")" << endl; delete[] data; size = _rhs.size; data = _rhs.data; _rhs.data = NULL; } return *this; } ~A() { cout << "Destructor (" << size << ")" << endl; if (data != NULL) { cout << "[Delete resources]" << endl; delete[] data; } } private: int *data; int size; }; int main() { vector<A> list; cout << "######### 1st ########" << endl; list.push_back(A(1)); cout << "######### 2nd ########" << endl; list.push_back(A(2)); cout << "######### 3rd ########" << endl; list[0] = A(3); cout << "######### Done ########" << endl; return 0; }
######### 1st ######### Normal constructor (1) Move constructor (1) Destructor (0) ######### 2nd ######### Normal constructor (2) Move constructor (2) Copy constructor (1) Destructor (1) [Delete resources] Destructor (0) ######### 3rd ######### Normal constructor (3) Move assignment operator (3) Destructor (0) ######### Done ######### Destructor (3) [Delete resources] Destructor (2) [Delete resources]
결과 log를 보면 실제 resource deallocation이 일어난 횟수는 1번 뿐이다. 그전엔 총 5번의 메모리 할당이 이뤄졌지만 move constructor & assignment를 통해 메모리 관리를 효율적으로 할 수 있었다.
코드상에서 큰 차이라고 한다면 A (A&& _rhs): data(NULL), size(0)
move constructor & operator가 추가가 되며 내부에 memory allocation (new
) & free (delete
) 이 없이 단순하게 address 전달만 해준다.
참고로 C++11 version 이상에선 기본적인 타입의 vector에서 move semantics (rvalue reference)가 추가됐기 때문에 성능상 높은 version compiler를 쓰는게 유리하다.
Reference
- https://psychoria.tistory.com/54
- https://welikecse.tistory.com/1