프로그래밍 언어/C++

C++ Copy and Swap idiom

ShovelingLife 2022. 8. 8. 20:12

C++에선 클래스가 리소스를 관리하고 클래스의 생성/소멸에 맞춰 리소스를 할당/해제한다. 관련된 멤버 함수와 그 역할은 아래와 같다.

  • 생성자: 리소스를 할당한다.
  • 복사 생성자: 리소스를 복사해온다.
  • 복사 대입 연산자: 기존 리소스를 해제하고 리소스를 복사해온다.
  • (C++11부터) 이동 생성자: 리소스를 가져온다.
  • (C++11부터) 이동 대입 연산자: 기존 리소스를 해제하고 리소스를 가져온다.
  • 소멸자: 리소스를 해제한다.

Copy-and-swap idiom은 간결하면서도 강력한 예외 안정성(strong exception safety)을 보장해주는 복사 대입 연산자를 짤 수 있게 해준다. 또한 이동 대입 연산자에를 짤 때에도 유용하다.

 

문자열 리소스를 관리하는 간단한 클래스를 생각해보자.

class Foo
{
    char* data = nullptr;

public:

    Foo() : data(new char[14])
    {
        std::strcpy(data, "Hello, World!");
    }

    Foo(const Foo& other) : data(new char[std::strlen(other.data) + 1])
    {
        std::strcpy(data, other.data);
    }

    ~Foo()
    {
        delete[] data;
    }
};

아래는 잘못된 코드이다.

Foo& operator=(const Foo& other)
{
    if (this != &other)
    {
        char* newData = new char[std::strlen(other.data) + 1];
        std::strcpy(data, other.data);
        delete[] data;
        data = newData;
    }
    return *this; ​
}

std::swap을 이용하도록 하자.

Foo& operator=(Foo other) // note: argument passed by value
{
    std::swap(data, other.data);
    return *this;
}

중복되는 교환 부분을 따로 정의하면서, ADL을 이용해 std::swap을 확장하였다(std::swap을 구체화하는 것은 표준 라이브러리에 간섭하는 것이므로 좋지 않다.

friend void swap(Foo& fst, Foo& snd)
{
    // enable ADL
    using std::swap;
    swap(fst.data, snd.data);
}

Foo(Foo&& other) : data(nullptr)
{
    swap(*this, other);
}

이동 대입 연산자도 결국엔 대입이라 복사 대입 연산자와 크게 다르지 않다. 다른 점은 other​를 복사하지 않는다는 점(리소스 할당이 없으므로 보통 예외가 발생하지 않는다)과 other​가 곧 소멸될 것이라는 점이다. 

class Foo
{
    char* data;

public:
    Foo() : data(new char[14])
    {
        std::strcpy(data, "Hello, World!");
    }

    Foo(const Foo& other) : data(new char[std::strlen(other.data) + 1])
    {
        std::strcpy(data, other.data);
    }

    Foo& operator=(Foo other) // note: argument passed by value
    {
        swap(*this, other);
        return *this;
    }

    Foo(Foo&& other) : data(nullptr)
    {
        swap(*this, other);
    }

    Foo& operator=(Foo&& other)
    {
        swap(*this, other);
        return *this;
    }

    ~Foo()
    {
        delete[] data;
    }

private:

    friend void swap(Foo& fst, Foo& snd)
    {
        // enable ADL
        using std::swap;
        swap(fst.data, snd.data);
    }

    friend std::ostream& operator<<(std::ostream& os, const Foo& foo)
    {
        os << foo.data;
        return os;
    }
};

int main()
{
    const Foo foo;
    std::cout << foo << '\n';
    return 0;
}

출처 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=stkov&logNo=220121638743