[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