R-값 참조 (Rvalue References)
R-값 참조는 C++11에서 도입된 기능으로, 임시 객체나 이동할 수 있는 리소스를 참조하는 데 사용됩니다. R-값 참조를 사용하면 불필요한 복사 연산을 줄이고, 성능을 최적화할 수 있습니다.
R-값 참조의 기본 사용법
R-값 참조는 &&
연산자를 사용하여 정의됩니다.
#include <iostream>
void printValue(int& x) {
std::cout << "L-value reference: " << x << std::endl;
}
void printValue(int&& x) {
std::cout << "R-value reference: " << x << std::endl;
}
int main() {
int a = 10;
printValue(a); // L-value reference
printValue(20); // R-value reference
printValue(a + 30); // R-value reference
return 0;
}
이 예제에서 printValue
함수는 L-값 참조와 R-값 참조를 모두 처리할 수 있습니다. a
는 L-값으로, 20
과 a + 30
은 R-값으로 전달됩니다.
이동 시멘틱 (Move Semantics)
이동 시멘틱은 R-값 참조를 활용하여 객체의 소유권을 이전하는 방법입니다. 이를 통해 불필요한 복사 연산을 피하고, 성능을 향상시킬 수 있습니다.
std::move
std::move
는 객체를 R-값으로 캐스팅하여 이동 시멘틱을 적용할 수 있도록 합니다.
#include <iostream>
#include <utility> // std::move
class MyClass {
public:
int* data;
MyClass(int size) : data(new int[size]) {
std::cout << "Constructor: allocated " << size << " ints." << std::endl;
}
~MyClass() {
delete[] data;
std::cout << "Destructor: freed memory." << std::endl;
}
// 복사 생성자
MyClass(const MyClass& other) : data(new int[1]) {
std::cout << "Copy constructor." << std::endl;
}
// 이동 생성자
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor." << std::endl;
}
};
int main() {
MyClass obj1(10);
MyClass obj2 = std::move(obj1); // 이동 생성자 호출
return 0;
}
이 예제에서 MyClass
는 이동 생성자를 정의하여 객체의 소유권을 이전합니다. std::move
를 사용하여 obj1
의 리소스를 obj2
로 이동시킵니다.
이동 대입 연산자
이동 대입 연산자는 이동 시멘틱을 사용하여 객체를 대입하는 연산자입니다.
#include <iostream>
#include <utility> // std::move
class MyClass {
public:
int* data;
MyClass(int size) : data(new int[size]) {
std::cout << "Constructor: allocated " << size << " ints." << std::endl;
}
~MyClass() {
delete[] data;
std::cout << "Destructor: freed memory." << std::endl;
}
// 복사 생성자
MyClass(const MyClass& other) : data(new int[1]) {
std::cout << "Copy constructor." << std::endl;
}
// 이동 생성자
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor." << std::endl;
}
// 이동 대입 연산자
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
std::cout << "Move assignment operator." << std::endl;
}
return *this;
}
};
int main() {
MyClass obj1(10);
MyClass obj2(20);
obj2 = std::move(obj1); // 이동 대입 연산자 호출
return 0;
}
이 예제에서 MyClass
는 이동 대입 연산자를 정의하여 객체의 소유권을 이전합니다. std::move
를 사용하여 obj1
의 리소스를 obj2
로 이동시킵니다.
완벽한 전달 (Perfect Forwarding)
완벽한 전달은 템플릿 함수를 통해 인자를 그대로 전달하는 기법입니다. 이를 통해 인자의 타입과 값 카테고리를 유지할 수 있습니다.
완벽한 전달 예제
#include <iostream>
#include <utility> // std::forward
void process(int& x) {
std::cout << "L-value reference process: " << x << std::endl;
}
void process(int&& x) {
std::cout << "R-value reference process: " << x << std::endl;
}
template <typename T>
void forwarder(T&& x) {
process(std::forward<T>(x));
}
int main() {
int a = 10;
forwarder(a); // L-value reference process
forwarder(20); // R-value reference process
forwarder(std::move(a)); // R-value reference process
return 0;
}
이 예제에서 forwarder
템플릿 함수는 std::forward
를 사용하여 인자를 그대로 전달합니다. 이를 통해 인자의 타입과 값 카테고리를 유지할 수 있습니다.
실습 문제
문제 1: R-값 참조를 사용하여 벡터의 요소를 이동
R-값 참조를 사용하여 벡터의 요소를 다른 벡터로 이동하는 프로그램을 작성하세요.
해설:
#include <iostream>
#include <vector>
#include <utility> // std::move
int main() {
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2;
for (int& x : vec1) {
vec2.push_back(std::move(x));
}
std::cout << "vec2: ";
for (const int& x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;
std::cout << "vec1: ";
for (const int& x : vec1) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
문제 2: 이동 생성자와 이동 대입 연산자를 정의하여 객체를 이동
이동 생성자와 이동 대입 연산자를 정의하여 객체를 이동하는 클래스를 작성하세요.
해설:
#include <iostream>
#include <utility> // std::move
class MyClass {
public:
int* data;
MyClass(int size) : data(new int[size]) {
std::cout << "Constructor: allocated " << size << " ints." << std::endl;
}
~MyClass() {
delete[] data;
std::cout << "Destructor: freed memory." << std::endl;
}
// 이동 생성자
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor." << std::endl;
}
// 이동 대입 연산자
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
std::cout << "Move assignment operator." << std::endl;
}
return *this;
}
};
int main() {
MyClass obj1(10);
MyClass obj2 = std::move(obj1); // 이동 생성자 호출
MyClass obj3(20);
obj3 = std::move(obj2); // 이동 대입 연산자 호출
return 0;
}
문제 3: 완벽한 전달을 사용하여 다양한 타입의 인자를 처리
완벽한 전달을 사용하여 다양한 타입의 인자를 처리하는 템플릿 함수를 작성하세요.
해설:
#include <iostream>
#include <utility> // std::forward
void process(int& x) {
std::cout << "L-value reference process: " << x << std::endl;
}
void process(int&& x) {
std::cout << "R-value reference process: " << x << std::endl;
}
template <typename T>
void forwarder(T&& x) {
process(std::forward<T>(x));
}
int main() {
int a = 10;
forwarder(a); // L-value reference process
forwarder(20); // R-value reference process
forwarder(std::move(a)); // R-value reference process
return 0;
}
이제 7일차의 학습을 마쳤습니다. R-값 참조와 이동 시멘틱에 대해 상세히 학습하고 실습 문제를 풀어보았습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일부터는 "디자인 패턴 심화"에 대해 다루겠습니다. 내일은 "싱글톤 패턴"에 대해 학습하겠습니다.