-
1. 이동 의미론(Move Semantics)Modern C++/Move Semantics 2020. 6. 29. 12:38
1. 이동 의미론
C++11 이후 C++ 의 가장 중요한 변화 중 하나는 복사를 통한 자원의 낭비를 줄이기 위해 객체 자원의 소유권을 이전 할 수 있는 이동 의미론이 추가되었다는 점이다.
class Foo {
};
위와 같이 Foo 클래스 정의 시 컴파일러는 자동으로 기본 생성자, 복사 생성자, 할당 연산자, 소멸자를 생성해 주는 데, C++11 이후에 는 여기에 이동 생성자, 이동 할당 연산자가 추가되었다.
또한 STL 라이브러리의 범용 유틸리티나, 컨테이너들에도 이동 생성자나, 이동 할당 연산자가 구현되어 복사로 인한 쓸데 없는 자원의 소비를 줄일 수 있게 되었다.
이동 생성자와 이동 할당 연산자는 아래와 같은 형태로 std::vector<T>의 예를 통해 확인 할 수 있다.
std::vector<T, Allocator>::vector 이동 생성자(move constructor) vector( vector&& other ); (since C++11)
(until C++17)vector( vector&& other ) noexcept; (since C++17) std::vector<T,Allocator>::operator = 이동 할당 연산자 (move assignment operator) vector& operator = ( vector&& other ); (since C++ 11)
(until C++ 17)vector& operator = ( vector&& other) noexcept; (since C++17) 2. 왼쪽 값(L-value) 와 오른쪽 값(R-Value)
이동 연산을 이해하기 위해서는 먼저 오른쪽 값과 왼쪽 값에 대해 구분할 수 있어야 한다.
왼쪽값
왼쪽값은
1. 기본적으로 할당 연산자(=)의 왼쪽에 위치 가능한 값으로 변수가 가장 대표적이다.
int a = 5; // a는 왼값(l-value) 2. 주소를 취할 수 있다.
int *ptr_a = &a // a의 주소를 취할 수 있다. 3. 왼값 참조에 바인딩 될 수 있다.
int &ref_a = a; 그 외 가능한 왼값
struct address {
std::string street, city, potal_code;
};
address addr;
addr.city = "Seoul"; // 객체의 멤버 변수도 왼쪽 값
int& get_count() {
static int count_ = 0;
return count_;
}
get_count() = 10; //왼값 참조를 반환하는 함수 호출도 왼쪽 값
오른쪽 값
오른쪽 값은
1. 할당 연산자(=)의 왼쪽에 위치할 수 없다.
int get_numeric( return 5; }
//오른 값은 절대 할당 연산자의 왼쪽에 있을 수 없다.
0 = a;
get_numeric() = 5;2. 주소를 취할 수 없다.
//오른 값의 주소는 취할 수 없다.
&5;
&get_numeric();3. 왼값 참조에 바인딩 될 수 없다.
//오른 값은 왼값 참조에 바인드 될 수 없다.
int& l_ref0 = 5;
int& l_ref1 = get_numeric();4. C++11 부터 소개된 오른 값 참조에 바인딩 될 수 있다.
int&& r_ref0 = 5;
int&& r_ref1 = get_numeric();
기본적으로 오른쪽 값은 임시 객체로 해당 객체를 구분할 구별자가 없어 주소나 참조 할 수 없다.std::vector 내 이동 생성자와 복사 생성자 비교
#include <iostream> #include <vector> using namespace std; std::vector<int> convert_container(int start, int end) { std::vector<int> v; v.reserve( end - start + 1); for(int i = start; i <= end; i++) { v.push_back(i); } return v; } void print_vector(const vector<int>& v) { std::cout << "{ "; for (int i = 0; i < v.size(); i++) { if(i != v.size() -1) { std::cout << v[i] << ", " ; } else { std::cout << v[i] << " "; } } std::cout << "}\n"; } std::vector<int> return_lvalue(int start, int end) { std::vector<int> v; v.reserve(end - start + 1); for(int i = start; i <= end; i++) { v.push_back(i); } return v; } int main() { std::vector<int> v1{1, 2, 3, 4, 5}; auto v2 = v1; // v1은 r-value 이므로 복사 생성자 호출 //v2 객체를 생성하기 위해 추가적인 복사 생성자가 호출된다. print_vector(v1); print_vector(v2); auto v3 = return_lvalue(1, 5); // return_lvalue 함수는 l-value를 반환하므로 이동 연산자 호출 //return_lvalue 에서 생성된 임시 객체의 리소스의 소유권을 v3로 이전 //v3 생성 시 이동 생성자 호출로 v3 내 추가적인 복사는 이루어지지 않는다. print_vector(v3); return 0; }
위와 같이 C++11 이 후 C++ 표준 컨테이너나 유틸리티는 이동 연산을 지원해 복사 최소화로 인한 성능 개선을 할 수 있도록 지원하고 있다.
'Modern C++ > Move Semantics' 카테고리의 다른 글
5. C++11 객체 생성 규칙 Rule of Five (0) 2020.07.01 4. 이동가능 객체(Movable Type) (0) 2020.06.30 3. 완벽 전달(Perfect Forwarding) (1) 2020.06.29 2. std::move (0) 2020.06.29