[C++ 공부] 이동 의미론

Yujin Lee·2022년 2월 19일
1

Cpp_Study

목록 보기
4/8
post-thumbnail

C++가 값 기반 언어로 진화해온 결과, 복사를 통한 자원의 낭비를 줄이기 위해 객체 자원의 소유권을 이전 할 수 있는 이동 의미론의 개념이 도입되었다. C++가 이동 의미론을 갖추지 못했다면 값 기반 의미론의 장점을 잃게 된다.

그 전에 복사 생성, 두 객체의 교환, 객체의 이동 생성 등의 차이점을 알아보자.

급하면 4. 이동 의미론으로 🏃🏻‍♀️🏃🏻‍♂️

1. 들어가기

1.1 객체 복사 생성(copy-construction)

객체를 복사할 때는 새로운 자원이 할당되고, 원본 객체와 복사된 객체는 완전히 구분돼야 한다.

auto a = Object();
auto b = a;  // 복사 생성

객체를 복사하면 초기화 -> 새로운 자원 할당 -> 데이터 복사 의 과정을 거친다.

가령, 원본 객체인 포인터가 자원 데이터인 {0, 1, 2}를 가리킨다고 해보자.
이를 복사한다면

  1. 대상 객체의 포인터는 일단 null을 가리킨다.
  2. 그 후 새로운 자원을 할당한다. {-1, -1, -1}
  3. 마지막으로 데이터를 복사한다. {0, 1, 2}

1.2 두 객체의 교환(swapping)

이동 의미론이 추가되기 전에는 swapping이 자원 할당이나 복사 없이 데이터를 전달하는 일반적인 방법이었다. 객체는 단순하게 내용을 서로 교환한다.

auto a = Object();
auto b = Object();
std::swqp(a, b);

객체 A가 가리키는 자원 {0, 1, 2, 3}
객체 B가 가리키는 자원 {4, 5, 6, 7}

Swap...

객체 A가 가리키는 자원 {4, 5, 6, 7}
객체 B가 가리키는 자원 {0, 1, 2, 3}

1.3 객체 이동 생성

객체를 이동할 때는 생성될 객체가 먼저 원본 객체의 자원을 가져간다. 그 후 원본 객체가 초기화된다.

auto a = Object();
auto b = std::move(a);  // b로 자원을 이동하도록 컴파일러에게 지시한다.


2. 3의 법칙(the rule of three)

C++에서 클래스와 자원 획득의 기본적인 개념 중 하나는 클래스가 반드시 자신의 자원을 완벽하게 관리해야 한다는 것이다.

클래스가 복사, 할당, 소멸 될 때 클래스는 해당 자원도 마찬가지로 복사, 할당, 해제
를 분명히 해야 한다. 이와 같은 세 가지 기능을 구현해야 한다~ 고 해서 3의 법칙 이라고 한다.

그 세가지를 정확히 말하면 다음과 같다.

  • 복사 생성자 (copy-constructor)
  • 복사 할당 (copy-assignment)
  • 소멸자 (destructor)

코드가 길면 읽기 싫어지지만...😥

3의 법칙이 float 배열의 자원을 어떻게 할당하고 해제하는지 살펴보자.

class Buffer {
public:
	// 생성자
    Buffer(const std::initializer_list<float>& values)
    : size_{value.size()} {
    	ptr_ = new float[value.size()];
        std::copy(values.begin(), values.end(), ptr_);
	}
	// 1. 복사 생성자
	Buffer(const Buffer& other) : size_{other.size_} {
		ptr_ = new float[size_];
    	std::copy(other.ptr_, other.ptr_ + size_, ptr_);
	}
	// 2. 복사 할당
	auto& operator=(const Buffer& other) {
		delete [] ptr_;
	    ptr_ = new float[other.size_];
	    size_ = other.size_;
	    std::copy(other.ptr_, other.ptr_ + size_, ptr_);
	    return *this;
	}
	//3. 소멸자
	~Buffer() {
    	delete [] ptr_;  // null 포인터를 삭제하는 것은 유효
        ptr_ = nullptr;
    }
    // 데이터 접근을 위한 반복 연산
    auto begin() const { return ptr_;}
    auto end() const { return ptr_ + size_; }
private:
	size_t size{0};
    float* ptr_{nullptr};
}

위에 긴 식이 어쨌건간에 이런 식으로 사용한다.

auto float_array = Buffer({0.0f, 0.5f, 1.0f, 1.5f});  // Buffer 객체 생성

auto func() {
	// 생성
    auto b0 = Buffer({0.0f, 0.5f, 1.0f, 1.5f});
    // 1. 복사 생성
    auto b1 = b0;
    // 2. 복사 할당, b0는 이미 초기화된 상태
    b0 = b1;
    // 3. 함수를 빠져나올 때에는 소멸자가 자동으로 호출된다.
}


3. 3의 법칙의 문제점

C++ 11 이전에는 자동적인 자원 획득이 3의 법칙이라는 지침 아래 수행됐다. 하지만 다음과 같은 문제점이 있다.

  • 복사할 수 없는 자원 : std::thread 클래스를 포함한 자원이라던가, 네트워크 연결 같은 건 객체를 넘겨주는 것이 불가능
  • 불필요한 복사 : 함수에서 Buffer 클래스를 반환하는 경우, 전체 배열이 복사돼야 한다.

이동 의미론이 없을 경우 포인터를 통해 할당하거나, 실제 클래스 대신 포인터를 전달해서 문제를 피했다. 하지만 이는 프로그래머가 포인터를 수동으로 처리해야 하는 문제, 코드에 포인터들이 가득 차게 되는 문제, 힙 할당이 늘어 느려지는 문제 등등 단점이 있다.



4. 이동 의미론

와 드디어 이동 의미론이다;

이동 의미론은 C++ 11 이전의 문제점을 해결하기 위해 등장했으며, 3의 법칙은 5의 법칙으로 확장됐다.

  • 복사 생성자 (copy-constructor)
  • 복사 할당 (copy-assignment)
  • 소멸자 (destructor)
  • 이동 생성자 (move-constructor)
  • 이동 할당 (move-assignment)

이동 생성자/이동 할당은 복사 생성자/복사 할당과는 달리 실제로 할당하거나 예외를 발생시킬 만한 작업을 수행하지 않는다.
복사할 원본 값들이 더 이상 사용되지 않는 것을 컴파일러가 감지하면 복사 대신 이동 생성자/이동 할당을 사용한다.
인터페이스는 복사할 때 분명하게 남아있지만, 내부적으로는 컴파일러가 단순한 이동을 수행한다. 우린 복사를 피하려고 굳이 어려운 포인터나 외부 파라미터를 사용할 필요가 없어진 것이다! 컴파일러가 자동으로 관리해준다.

단, 이동 생성자/이동 할당 작업자를 noexcept로 꼭! 표시해야 한다.
이걸 안쓰면 STL 컨테이너와 알고리즘이 특정 조건에서는 그냥 복사/할당에 의존하게 된다.

class Animal {
public:
	Animal() {}
    auto set_song(const std::string& s) { song_ = s; }
    auto set_song(std::string&& s) { song_ = std::move(s); }
    std::string song_;
};
auto animal = Animal();

복사 할당 예시

auto make_dog_song() { return std::string{"I'm a dog"}; }
animal.set_song(dog_song_a);

이동 할당 예시

auto cat_a = std::string{"I'm a cat"};
animal.set_song(std::move(cat_a));

하지만 실질적으로 우리가 복사/이동 생성자와 복사/이동 할당을 직접 작성하는 경우는 드물다. 명시적으로 기재된 복사 생성자, 복사 할당, 이동 생성자, 이동 할당, 소멸자가 필요없는 자신만의 클래스를 작성하는 것을 영(0)의 법칙 이라고 한다.






와 이거 이후 책 내용은 도통 뭔소린지 모르겠다....담에 다시 도전👊🏻

profile
I can be your Genie🧞‍♀️ How ‘bout Aladdin? 🧞‍♂️

0개의 댓글