[C/C++] &&(Rvalue Reference), 이동 의미론(Move Semantics)

할랑말랑·2026년 3월 10일

C/C++

목록 보기
15/45

&&(Rvalue Reference)

임시 객체나 이동 가능한 값을 참조하는 참조 타입이다
C++11부터 도입되어 이동 의미론(Move Semantics)과 완벽한 전달(Perfect Forwarding)을 가능하게 합니다.

&& 의 핵심

1. 임시 객체의 수명 연장

#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;
}
  • && 참조로 임시 객체의 수명을 연장해서 문제를 해결할 수 있다.

2. 이동 의미론(Move Semantics)

객체의 소유권을 이전하는 것을 의미한다. rvalue 참조를 사용하여 자원을 효율적으로 이동시킬수 있어 성능을 개선한다.

이동 생성자 / 이동 대입 연산자

이동 생성자(Move Constructor)와 이동 대입 연산자(Move Assignment Operator)는 C++11부터 도입된 기능으로, 임시 객체(R-value)의 자원을 복사 대신 "이동"시켜 메모리 할당 및 복사 비용을 줄여 성능을 향상시킨다. 주로 포인터 등 자원의 소유권을 이전하며, 이동 후 원본 객체는 유효하지만 빈 상태로 둔다.

특징

  • 깊은복사 대신 얕은 복사를 사용하여 대형 객체 이동 시 효율적
  • 임시 객체 rvalue가 생성되거나 대입될 때 호출
  • std::move : 객체를 rvalue로 캐스팅하여 이동/대입 연산자 명시적으로 호출
#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;
}

3. 완벽한 전달 (Perfect Forwarding)

템플릿 함수에서 rvalue 참조를 사용하여 인자를 전달할 때, 인자의 값 카테고리를 유지할 수 있는 기능이다
이를 통해 함수가 인자를 이동할지 복사할지를 결정하게 할 수 있다.

0개의 댓글