Menu
Coddy logo textTech

이동 시맨틱과 Rvalue

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 83번째.

C++에서 모든 표현식은 lvalue(지속적인 식별성을 가지며 주소를 지정할 수 있음) 또는 rvalue(임시적이며 곧 파괴됨) 중 하나입니다. 이러한 구분을 이해하면 불필요한 복사를 방지하는 강력한 최적화 기능인 이동 의미론(move semantics)을 활용할 수 있습니다.

&&로 선언되는 우측값 참조(rvalue reference)는 임시 객체에 특별히 바인딩됩니다. 이는 어차피 곧 사라질 객체로부터 리소스를 "훔칠" 수 있게 해줍니다:

#include <iostream>
#include <utility>

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : data(new int[s]), size(s) {
        std::cout << "Constructed\n";
    }
    
    // 이동 생성자 - 자원을 가져옵니다
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // 소스를 유효한 상태로 남겨둡니다
        other.size = 0;
        std::cout << "Moved\n";
    }
    
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer b1(1000);
    Buffer b2(std::move(b1));  // 이동 생성자를 트리거합니다
}

std::move 함수는 실제로 아무것도 이동시키지 않습니다. 단순히 lvalue를 rvalue reference로 캐스팅하여, 객체의 리소스를 포기할 의사가 있음을 알릴 뿐입니다. 실제 이동은 이동 생성자나 이동 대입 연산자에서 일어납니다.

이동 의미론(Move semantics)은 컨테이너나 문자열과 같이 리소스 집약적인 객체를 다룰 때 성능을 획기적으로 향상시킵니다. 수 메가바이트의 데이터를 깊은 복사(deep copy)하는 대신, 단순히 포인터 소유권을 이전하며, 이는 데이터 크기에 관계없이 상수 시간(constant-time) 내에 완료되는 작업입니다.

challenge icon

챌린지

쉬움

이동 의미론(move semantics)이 실제로 어떻게 작동하는지 보여주는 리소스 관리 DataBuffer 클래스를 만들어 보겠습니다. 동적으로 할당된 메모리의 소유권을 이전할 때 복사 대신 이동을 사용함으로써 효율성을 얼마나 획기적으로 향상시킬 수 있는지 확인하게 될 것입니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • DataBuffer.h: 동적으로 할당된 정수 배열을 관리하는 DataBuffer 클래스를 정의합니다.

    클래스에는 데이터 포인터(int*), 크기(size_t), 그리고 작업 중에 어떤 버퍼인지 추적하는 데 도움이 되는 이름(std::string)을 위한 private 멤버가 있어야 합니다.

    다음 항목들을 선언하세요:

    • std::string 이름과 size_t 크기를 인자로 받아 배열을 할당하고 다음을 출력하는 생성자: [name] constructed with size [size]
    • 우측값 참조(rvalue reference)를 인자로 받아 리소스를 훔쳐오고 다음을 출력하는 이동 생성자: [name] moved from [source_name] (이동된 버퍼는 소스 버퍼의 이름을 가져옵니다)
    • [name] destroyed를 출력하는 소멸자 (버퍼가 이동된 상태라면 empty destroyed 출력)
    • 현재 크기를 반환하는 getSize() 메서드
    • 버퍼의 이름을 반환하는 getName() 메서드

    이동 생성자를 noexcept로 표시하고, 소스 객체는 유효한 빈 상태(nullptr, 크기 0, 이름 "empty")로 남겨두어야 함을 기억하세요.

  • DataBuffer.cpp: 헤더에 선언된 모든 메서드를 구현합니다. 소멸자가 실행될 때 포인터가 null이 아닌 경우에만 데이터를 삭제하세요. 출력을 위해 <iostream>을 포함합니다.
  • main.cpp: 두 개의 입력을 읽습니다:
    1. 버퍼의 이름 (문자열)
    2. 버퍼의 크기 (정수)

    주어진 이름과 크기로 DataBuffer를 생성합니다. 그런 다음 std::move()를 사용하여 첫 번째 버퍼에서 이동하여 두 번째 버퍼를 생성합니다. 이동 후 두 버퍼의 상태를 출력합니다:

    • Original: [name] size=[size]
    • New: [name] size=[size]

    std::move를 사용하기 위해 <utility>를 포함합니다.

예를 들어, 입력값이 Alpha100인 경우:

Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyed

입력값이 Buffer50인 경우:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

이동 생성자가 데이터를 복사하지 않고 할당된 메모리의 소유권을 어떻게 이전하는지 주목하세요. 원래 버퍼는 비어 있지만 유효한 상태로 남게 되며, 프로그램 종료 시 두 버퍼가 모두 소멸될 때 메모리를 여전히 소유하고 있는 버퍼만 실제로 메모리를 삭제합니다.

치트 시트

C++ 표현식은 lvalues(지속적, 주소 지정 가능) 또는 rvalues(임시적) 중 하나입니다. 이동 의미론(Move semantics)은 임시 객체로부터 리소스를 복사하는 대신 전송함으로써 성능을 최적화합니다.

Rvalue 참조

&&로 선언되는 rvalue 참조는 임시 객체에 바인딩됩니다:

Buffer(Buffer&& other)  // Rvalue 참조 매개변수

이동 생성자

임시 객체로부터 리소스를 가져옵니다. noexcept로 표시하고 소스 객체를 유효한 빈 상태로 남겨둡니다:

Buffer(Buffer&& other) noexcept 
    : data(other.data), size(other.size) {
    other.data = nullptr;  // 소스를 유효한 상태로 유지
    other.size = 0;
}

std::move

lvalue를 rvalue 참조로 캐스팅하여 리소스를 전송할 의사가 있음을 알립니다. <utility>를 포함해야 합니다:

Buffer b1(1000);
Buffer b2(std::move(b1));  // 이동 생성자를 트리거함

std::move 자체는 아무것도 이동시키지 않습니다. 실제 전송은 이동 생성자나 이동 대입 연산자에서 일어납니다.

장점

이동 의미론은 객체 크기에 관계없이 상수 시간 리소스 전송을 제공하여, 대규모 데이터 구조의 비용이 많이 드는 깊은 복사를 방지합니다.

직접 해보기

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: 주어진 name과 size를 사용하여 DataBuffer를 생성하세요
    
    // TODO: std::move()를 사용하여 첫 번째 버퍼를 이동시켜 두 번째 버퍼를 생성하세요
    
    // TODO: 두 버퍼의 상태를 출력하세요:
    // Original: [name] size=[size]
    // New: [name] size=[size]
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨