티스토리 뷰

이동 시맨틱 (Moving Semantics)

깊은 복사와 얕은 복사의 개념을 먼저 공부하면, 이동 시맨틱이라는 개념을 알아볼 수 있다.

 

우선 깊은 복사란, 하나의 변수 값을 다른 변수에 완벽히 복제하여 두 변수가 독립적인 형태로 메모리에 할당되는 것이다.

얕은 복사란, 하나의 변수 값을 가르키는 주소 값을 다른 변수도 동일하게 가지게되어 두 변수가 동일한 메모리 영역을 가리키게 되는 것이다.

 

이동 시맨틱은 객체의 소유권을 다른 객체로 옮기는 방법이며, 불필요한 데이터 복사를 피하고 성능을 향상 시킨다. 그러면 다음 예제를 보자.

 

이중 깊은 복수
class MyString {
public:
    char* getName() {
    	string name = "Hong";
    	return name;
    }
};

int main() {
    MyString str1;
	
    string name = str1.getName();
	
    return 0;
}

 

위 코드에서 우리는 getName이라는 함수가 호출되면 name이라는 변수에 "Hong"이라는 값이 깊은 복사되고, Return By Value로 인해 반환 값이 깊은 복사가 또 일어나는 것을 볼 수 있다. 즉, 복사할 때마다 임시 객체를 생성하고 메모리에 할당되고 해제된다.

 

2번의 깊은 복사

 

 


이동 시맨틱
#include <iostream>
#include <utility>
#include <cstring>

using namespace std;

class MyString {
private:
    char* data;
    size_t size;
    
public:
    // 생성자: 문자열을 동적으로 할당
    MyString(const char* str) {
        size = strlen(str);
        data = new char[size + 1];
        strcpy(data, str);
    }

    // 이동 생성자: 다른 MyString 객체로 이동
    MyString(MyString&& other) noexcept {
        cout << "이동 생성자 호출" << endl;
        data = other.data;
        size = other.size;
        other.data = nullptr;  // 소유권 이전
        other.size = 0;
    }

    // 이동 대입 연산자: 다른 MyString 객체로 이동
    MyString& operator=(MyString&& other) noexcept {
        cout << "이동 대입 연산자 호출" << endl;
        if (this != &other) {
            delete[] data;  // 기존 데이터 삭제
            data = other.data;
            size = other.size;
            other.data = nullptr;  // 소유권 이전
            other.size = 0;
        }
        return *this;
    }

    // 소멸자: 동적으로 할당된 메모리 해제
    ~MyString() {
        delete[] data;
    }
    
    void print(int num) {
        cout << num << " : " << size << ", ";
        if (data) {
            cout << data;
        } else {
            cout << "null";
        }
        cout << endl;
    }
};

int main() {
    MyString str1("Hello, World!");
    // 이동 생성자 호출
    MyString str2 = move(str1);
    // 이동 대입 연산자 호출
    MyString str3("Another string");
    
    str3 = move(str2);
    
    str1.print(1);
    str2.print(2);
    str3.print(3);

    return 0;
}
이동 생성자 호출
이동 대입 연산자 호출
1 : 0, null
2 : 0, null
3 : 13, Hello, World!

 

위 코드에서 생성자와 연산자 구문은 동일한 클래스("MyString")을 참조하기에 서로의 private 멤버에 접근이 가능한 상태이다. 그리고 각 클래스 객체는 더블 레퍼런스('T&&')형태로 인자가 전달되어 Call By Reference로 참조 인자가 전달된다.

 

참조 인자가 전달되어 추가적 포인터 할당이나 복사가 일어나지 않는다. 이제 각 생성자와 연산자 내에서 private 변수들의 소유권을 모두 가져오고 원래 객체의 데이터를 제거한다. 이과정을 거친다면 포인터 주소를 가진 변수가 이동하여 소유권이 가장 마지막으로 이동 받은 객체에게 넘어간다.

 

이로써 추가적인 메모리 할당이 없고, 임시 객체를 제거하여 메모리 누수가 발생하지 않도록 만드는 방법을 알아보았다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함