임시 객체나 이동 가능한 값을 참조하는 참조 타입이다
C++11부터 도입되어 이동 의미론(Move Semantics)과 완벽한 전달(Perfect Forwarding)을 가능하게 합니다.
#include <iostream>
using namespace std;
class Object
{
public:
int value;
Object(int v) : value(v)
{
cout << "생성자: " << value << " (this: " << this << ")" << endl;
}
~Object()
{
cout << "소멸자: " << value << " (this: " << this << ")" << endl;
}
};
int main()
{
cout << "=== 1. 참조 없이 임시 객체 ===" << endl;
Object(10); // 생성하자마자 즉시 소멸
cout << "다음 줄" << endl;
cout << "\n=== 2. rvalue 참조로 수명 연장 ===" << endl;
Object&& rref = Object(20); // 수명 연장!
cout << "rref.value = " << rref.value << endl;
cout << "아직 살아있음!" << endl;
cout << "\n=== main 종료 ===" << endl;
return 0;
}
나쁜 예 - 즉시 소멸되는 문제
#include <iostream> #include <cstring> using namespace std; class String { private: char* data; public: String(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); cout << "생성: " << data << endl; } ~String() { cout << "소멸: " << (data ? data : "nullptr") << endl; delete[] data; } const char* c_str() const { return data; } }; String createString() { return String("Hello World"); } int main() { cout << "=== 문제 상황 ===" << endl; // 임시 객체가 즉시 소멸됨! const char* text = createString().c_str(); String aa = createString(); //cout << aa.c_str() << endl; // 위험! 이미 소멸된 객체의 메모리 접근 cout << "text: " << text << endl; // 쓰레기 값 또는 크래시! return 0; }
text는 creatsString함수에서 반환받은 임시객체는 c_str 함수 에서 data를 반환하지만 그이후 ; 세미콜론을 만나고 임시객체가 소멸된다. 그때 소멸자에서는 data의 동적할당을 해제하게 되고 text는 댕글링 포인터가 된다.
aa는 creatsString함수에서 반환받은 임시객체는 기본 복사생성자로 얕은복사로 임시객체가 가리키는 것을 aa가 가리키게된다. 그리고 임시객체는 소멸하지만 문제가 생기지 않는다.
&&참조
int main() { String&& temp = createString(); const char* text = temp.c_str(); cout << "text: " << text << endl; return 0; }
- && 참조로 임시 객체의 수명을 연장해서 문제를 해결할 수 있다.
객체의 소유권을 이전하는 것을 의미한다. rvalue 참조를 사용하여 자원을 효율적으로 이동시킬수 있어 성능을 개선한다.
이동 생성자(Move Constructor)와 이동 대입 연산자(Move Assignment Operator)는 C++11부터 도입된 기능으로, 임시 객체(R-value)의 자원을 복사 대신 "이동"시켜 메모리 할당 및 복사 비용을 줄여 성능을 향상시킨다. 주로 포인터 등 자원의 소유권을 이전하며, 이동 후 원본 객체는 유효하지만 빈 상태로 둔다.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class String
{
private:
char* data;
public:
String(const char* str)
{
data = new char[strlen(str) + 1];
strcpy(data, str);
cout << "생성자: " << data << endl;
}
~String()
{
cout << "소멸자: " << (data ? data : "nullptr") << endl;
delete[] data;
}
// 복사 생성자
String(const String& other)
{
cout << ">>> 복사 생성자 (& 버전) <<<" << endl;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << " 새 메모리 할당: " << (void*)data << endl;
}
// 이동 생성자
String(String&& other) noexcept
{
cout << ">>> 이동 생성자 (&& 버전) <<<" << endl;
data = other.data;
other.data = nullptr;
cout << " 포인터만 훔침: " << (void*)data << endl;
}
// 이동 대입 연산자
String& operator=(String&& other) noexcept
{
std::cout << ">>> 이동 대입 연산자 <<<" << std::endl;
if (this != &other) // 자기 대입 방지
{
delete[] data; // 기존 자원 해제
data = other.data; // 자원 소유권 이전
other.data = nullptr; // 원래 객체의 포인터를 null로 설정
}
return *this;
}
};
String createString()
{
return String("Temp");
}
int main()
{
String s1("Hello");
// 복사 생성자
String s2 = s1;
String s3 = String("World");
// String("World")는 임시 객체로 rvalue
// C++에서는 rvalue 참조를 직접 변수에 할당할 수 있지만, 이렇게 할 경우 이동 생성자가 호출되지 않는다.
// 이동 생성자는 rvalue를 다른 객체에 할당할 때 호출된다.
String s4 = std::move(s3); // std::move는 rvalue로 변환 -> && 버전
String s5("22");
// 이동 대입 연산자
s5 = std::move(s4);
cout << "\n=== main 종료 ===" << endl;
return 0;
}
템플릿 함수에서 rvalue 참조를 사용하여 인자를 전달할 때, 인자의 값 카테고리를 유지할 수 있는 기능이다
이를 통해 함수가 인자를 이동할지 복사할지를 결정하게 할 수 있다.