lvalue와 rvalue는 각각 copy semantic, move semantic과 큰 관련이 있다.
Lvalue와 Rvalue
std::string a = "abc"; // =을 기준으로 왼쪽에 있으면 lvalue, 오른쪽에 있으면 rvalue
std::string b = a; // 둘 다 lvalue
rvalue: 메모리 공간에 넣을 데이터 값. 한 번 쓰고 버릴 temporary한 값.
lvalue : rvalue가 들어갈 공간. 우리가 계속 불러서 쓸 수 있는 변수.
lvalue는 & 하나, rvalue는 && 두 개
그래서 lvalue는 어떠한 메모리 공간을 가리키고 있기 때문에 해당 메모리 공간의 값을 복사(copy semantic)해서 data container(예로 vector)에 넣을 수 있다.
그러나 위 코드의 "abc"와 같이 일회성으로 존재하는 rvalue는 메모리 공간을 가리키고 있지 않기 때문에 copy가 불가능하다. 그래서 바로 "abc"가 선언된 자리를 바로 가리키게 된다. 때문에 copy보다 더 경제적인 연산이다.
move() 함수
std::move는 Lvalue를 rvalue로 변경시킬 수 있는 연산자이다.
int main()
{
std::string a = "string";
std::string b = std::move(a);
}
a라는 변수는 원래 "string"값이 적재된 메모리 공간을 가리키고 있었지만, std::move를 사용함으로서 그 포인팅이 끊어지게 된다.
그리고 새로 대입되는 b가 "string"을 가리키게 된다. 이 때 a는 아무것도 가리키는 게 없기 때문에 size가 0인 빈 변수가 된다.
move semantic이 C++11부터 도입되면서 class 생성자에도 기존의 copy constructor 말고도 move constructer를 지원한다.
Copy Constructor VS Move Constructor
오브젝트를 만들 때, 파라미터로 rvalue를 패싱하면 move constructor가 호출되면서 좀 더 efficient하게 코드를 짤 수 있다.
디폴트로 제공되는 것들, Rule of Three(Five)
디폴트로 제공되는 것들, Rule of Three(Five)
Copy constructor, Copy assignment, Destructor (Move constructor, Move assignment)
아래 코드 예시를 살펴보면,
class Person
{
public:
Person(std::string name, int age) // (1)
:name(std::move(name)), age(age) {};
Person(const Person& other) // (2)
:name(other.name), age(other.age) {};
Person(Person&& other) // (3)
:name(std::move(other.name)), age(other.age) {};
private:
std::string name;
int age;
};
int main()
{
Person a("thirtyone", 31);
Person b(a);
Person c{std::move(a)};
}
첫번째 Person a 생성 : copy constructor에서는 파라미터로 받은 lvalue를 std::string name에 copy가 되고, 이후 클래스 프로퍼티의 name에 넣을 때에는 copy 대신 std::move를 사용해 move 연산으로 바꿔주고 있다.
두번째 Person b 생성 : b(a) 처럼 파라미터로 lvalue를 넘겨주었고, (2)번 생성자가 불림으로써 other.name과 other.age 값이 b 내부로 복사된다.
세번째 Person c 생성 : c(std::move(a))처럼 부르는 경우, 먼저 std::move(a)로 a는 rvalue가 되어 (3)번 생성자가 불린다. 이후 move constructor 내에서 멤버변수 name을 초기화할 때 string 값이었던 other.name이 move()로 인하여 rvalue로 바뀌게 되고, 이 때 원래 a가 가리키고 있던 값이었던 "thirtyone"은 c가 가져가서 c의 name의 초기화값이 "thirtyone"이 된다. 즉, c가 a의 name을 낚아채서 가리키게 되는 것. (age는 복사)
따라서 a의 객체 안에서는 age값은 31이 복사가 되어 값을 가지고 있지만, name은 c에게 뺏겼기 때문에 아무 값도 가지고 있지 않다.
b는 a에서 전부 복사했기 때문에 온전히 다 가지고 있으며,
c 또한 a에게서 가져온 name과 복사해온 age 둘 다 갖고 있다.
'C++' 카테고리의 다른 글
std::vector 란? (0) | 2024.10.23 |
---|---|
해쉬 테이블 (1) | 2024.02.11 |
Functor에 대하여 (Function Objects) (0) | 2023.01.23 |