c++ - 복사 및 스왑 이디엄이란 무엇입니까?





copy-constructor assignment-operator c++-faq copy-and-swap (5)


과제의 중심은 두 단계입니다. 즉, 객체의 이전 상태를 찢어 내고 새로운 객체를 다른 객체 상태의 사본으로 작성하는 것 입니다.

기본적으로 소멸자복사 생성자 가하는 일이므로 첫 번째 아이디어는 작업을 위임자에게 위임하는 것입니다. 그러나 파괴가 실패하지 않아야하기 때문에 건설이 진행되는 동안 우리는 실제로 반대 방향으로하고 싶습니다 . 먼저 건설 적인 부분을 수행하고 성공한 경우 파괴적인 부분을 수행하십시오 . 복사 및 스왑 (copy-and-swap) 관용법은이를 수행하는 방법입니다. 먼저 클래스의 복사 생성자를 호출하여 임시를 생성 한 다음 임시로 해당 데이터를 교체 한 다음 임시 소멸자가 이전 상태를 파괴하도록합니다.
swap() 은 결코 실패하지 않기 때문에 실패 할 수도있는 유일한 부분은 복사 생성입니다. 먼저 수행되고 실패 할 경우 대상 객체에서 아무 것도 변경되지 않습니다.

정제 된 형식에서 복사 및 스왑은 할당 연산자의 (비표준) 매개 변수를 초기화하여 복사를 수행함으로써 구현됩니다.

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}

이 관용구는 무엇이며 언제 사용해야합니까? 어떤 문제가 해결됩니까? C ++ 11을 사용하면 관용어가 변경됩니까?

비록 그것이 여러 곳에서 언급되었지만, 우리는 단 하나의 "what is it"이라는 질문과 대답을 가지고 있지 않았습니다, 그래서 여기 있습니다. 이전에 언급 한 장소의 일부 목록은 다음과 같습니다.




이 대답은 위의 답변에 대한 추가 및 약간의 수정과 같습니다.

Visual Studio의 일부 버전 (및 기타 컴파일러)에는 실제로 성가신이며 의미가없는 버그가 있습니다. 따라서 다음과 같이 swap 함수를 선언하거나 정의하면 :

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... swap 함수를 호출하면 컴파일러에서 소리를 지르겠습니다.

friend 함수가 호출되고이 객체가 매개 변수로 전달되는 것과 관련이 있습니다.

이 문제를 해결하는 방법은 friend 키워드를 사용하지 않고 swap 함수를 재정의하는 것입니다.

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

이번에는 swap 호출하고 other 넘겨 컴파일러를 행복하게 만들 수 있습니다.

결국, 당신은 2 개체를 교환하는 friend 기능을 사용할 필요 가 없습니다. 하나의 other 객체를 매개 변수로 가지는 멤버 함수를 swap 으로 만드는 것은 많은 의미가 있습니다.

this 객체에 이미 액세스 할 수 있으므로 매개 변수로 전달하는 것은 기술적으로 중복됩니다.




개요

복사 및 스왑 이디엄이 필요한 이유는 무엇입니까?

리소스 (스마트 포인터와 같은 래퍼) 를 관리하는 모든 클래스는 The Big Three 를 구현해야합니다. 복사 생성자와 소멸자의 목표와 구현은 간단하지만 복사 할당 연산자는 가장 미묘하고 어려울 것입니다. 어떻게해야합니까? 어떤 함정을 피해야합니까?

카피 - 스왑 (copy-and-swap) 관용구 는 솔루션이며, 할당 연산자가 코드 중복을 피하고 강력한 예외 보장을 제공한다는 두 가지를 달성하는 데 우아하게 도움을줍니다.

어떻게 작동합니까?

Conceptually 복사 생성자의 기능을 사용하여 데이터의 로컬 복사본을 만든 다음 복사 된 데이터를 swap 함수로 가져와 이전 데이터를 새 데이터로 바꿉니다. 그런 다음 임시 복사본이 삭제되고 이전 데이터가 삭제됩니다. 새로운 데이터의 복사본이 남아 있습니다.

copy-and-swap 관용구를 사용하려면 작업 복사본 생성자, 작업 소멸자 (둘 다 래퍼의 기초이므로 어쨌든 완료해야 함)와 swap 함수의 세 가지가 필요합니다.

스왑 함수는 클래스의 두 객체 (멤버의 멤버)를 서로 바꿔주는 비 던진 (non-throwing) 함수입니다. 우리 자신을 제공하는 대신 std::swap 을 사용하도록 유혹 될 수도 있지만 이는 불가능할 수 있습니다. std::swap 은 구현 내에서 복사 생성자와 복사 할당 연산자를 사용하며 궁극적으로 할당 연산자를 자체적으로 정의하려고합니다.

(그뿐만 아니라 스 swap 부적합한 호출은 std::swap 이 필요로하는 클래스의 불필요한 생성 및 파기를 건너 뛰고 커스텀 스왑 연산자를 사용할 것입니다.)

심층 설명

목표

구체적인 경우를 생각해 봅시다. 우리는 무의미한 클래스에서 동적 배열을 관리하려고합니다. 우리는 작업 생성자, 복사 생성자 및 소멸자로 시작합니다.

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr),
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

이 클래스는 배열을 거의 성공적으로 관리하지만 올바르게 작동하려면 operator= 가 필요합니다.

실패한 솔루션

다음은 순진한 구현 방식을 보여줍니다.

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = nullptr; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : nullptr; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
}

그리고 우리는 우리가 끝났다고 말한다. 이제는 누출없이 어레이를 관리합니다. 그러나 코드에서 (n) 으로 순차적으로 표시되는 세 가지 문제가 있습니다.

  1. 첫 번째는 자체 할당 테스트입니다. 이 검사는 두 가지 목적을 수행합니다. 즉, 자체 할당시 불필요한 코드를 실행하지 못하도록하는 쉬운 방법이며 미묘한 버그 (예 : 배열을 삭제하고 복사하는 등)로부터 우리를 보호합니다. 그러나 다른 모든 경우에는 프로그램을 느리게 실행하고 코드에서 소음으로 작용합니다. 자체 할당이 거의 발생하지 않으므로 대부분이 시간을 낭비합니다. 운영자가 없이도 제대로 작동한다면 더 좋을 것입니다.

  2. 두 번째는 기본 예외 보증 만 제공한다는 것입니다. new int[mSize] 가 실패하면 *this 수정됩니다. (즉, 크기가 잘못되어 데이터가 사라졌습니다!) 강력한 예외 보증을 위해서는 다음과 같은 것이 필요합니다.

    dumb_array& operator=(const dumb_array& other)
    {
        if (this != &other) // (1)
        {
            // get the new data ready before we replace the old
            std::size_t newSize = other.mSize;
            int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
            std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
    
            // replace the old data (all are non-throwing)
            delete [] mArray;
            mSize = newSize;
            mArray = newArray;
        }
    
        return *this;
    }
    
  3. 코드가 확장되었습니다! 따라서 세 번째 문제가 생깁니다 : 코드 중복. 할당 연산자는 이미 다른 곳에서 작성한 모든 코드를 효과적으로 복제합니다. 이것은 끔찍한 일입니다.

우리의 경우 핵심은 두 줄 (할당 및 사본)이지만 더 복잡한 리소스를 사용하면이 코드가 부 풀리는 것은 상당히 번거로운 작업이 될 수 있습니다. 우리는 결코 반복하지 않기 위해 노력해야합니다.

(하나의 리소스를 올바르게 관리하기 위해 많은 코드가 필요하다면, 내 클래스가 하나 이상의 리소스를 관리한다면 어떨까요? 이것은 중요한 문제인 것처럼 보일 수 있지만 실제로는 try / catch 절을 필요로하지 않습니다. 클래스는 하나의 리소스 만 관리해야하기 때문입니다!)

성공적인 솔루션

앞서 언급했듯이 복사 및 스왑 이디엄은 이러한 모든 문제를 해결합니다. 하지만 지금은 swap 기능을 제외한 모든 요구 사항이 있습니다. The Rule of Three는 성공적으로 복사 생성자, 할당 연산자 및 소멸자의 존재를 수반하지만 실제로는 "Big Three and A Half"라고해야합니다 : 클래스가 리소스를 관리 할 때마다 swap 을 제공하는 것이 좋습니다 기능.

클래스에 스왑 기능을 추가해야합니다. † 다음과 같이합니다.

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;

        // by swapping the members of two objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

(왜 public friend swap 하는지에 대한 설명이 있습니다.) 이제 우리는 우리의 dumb_array 을 바꿀 수있을뿐만 아니라, 일반적으로 스왑이 더 효율적일 수 있습니다. 전체 배열을 할당하고 복사하는 것이 아니라 단순히 포인터와 크기를 서로 바꿉니다. 기능과 효율성면에서이 보너스를 제외하고 이제 복사 및 스왑 이디엄을 구현할 준비가되었습니다.

추측없이, 우리의 대입 연산자는 다음과 같습니다.

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
}

그리고 그게 다야! 하나가 급습하자 세 가지 문제가 모두 우아하게 처리되었습니다.

왜 작동합니까?

우리는 우선 중요한 선택을 주목합니다 : 매개 변수 인수는 값에 의해 취해진 것입니다. 그럼에도 불구하고 다음과 같은 작업을 쉽게 수행 할 수 있습니다 (실제로 많은 숙련 된 구현 방식이 가능합니다).

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

우리는 중요한 최적화 기회를 놓치게됩니다. 뿐만 아니라이 선택 사항은 나중에 설명 할 C ++ 11에서 중요합니다. (일반적으로 매우 유용한 지침은 다음과 같습니다 : 함수에서 무언가의 복사본을 만들려면 컴파일러가 매개 변수 목록에서이를 수행하도록하십시오.) ‡)

어느 쪽이든, 우리의 자원을 얻는이 방법은 코드 중복을 제거하는 열쇠입니다. 복사 생성자의 코드를 사용하여 복사본을 만들고, 그 코드를 반복 할 필요가 없습니다. 사본이 만들어지기 때문에, 우리는 교환 할 준비가되었습니다.

새로운 데이터가 모두 이미 할당되고 복사되고 사용될 준비가되었다는 것을 함수에 입력하면 관찰하십시오. 이것이 우리에게 무료로 강력한 예외 보증을 제공하는 것입니다. 복사의 구성이 실패하면 함수를 입력하지 않기 때문에 *this 상태를 변경할 수 없습니다. (우리가 강력하게 예외를 보장하기 위해 수동으로했던 것은 컴파일러가 지금 우리를 위해하고있는 것입니다.)

swap 은 비 던지기 때문에이 시점에서 우리는 집이 없습니다. 현재 데이터를 복사 된 데이터와 교환하여 상태를 안전하게 변경하고 오래된 데이터를 임시로 가져옵니다. 그런 다음 함수가 반환 될 때 이전 데이터가 해제됩니다. (여기서 매개 변수의 범위가 끝나고 소멸자가 호출됩니다.)

관용구는 코드를 반복하지 않기 때문에 연산자 내에 버그를 도입 할 수 없습니다. 이는 우리가자가 할당 검사의 필요성을 없앰으로써 operator= 단일하게 구현할 수 있음을 의미합니다. (또한 비 자체 할당에 대한 성능 저하는 더 이상 발생하지 않습니다.)

이것이 복사 및 스왑 이디엄입니다.

C ++ 11은 어떻습니까?

C ++의 차기 버전 인 C ++ 11은 우리가 자원을 관리하는 방법에있어 매우 중요한 변화 중 하나입니다. 규칙 3은 이제 4의 규칙입니다 . 왜? 자원을 복사 할 수 있어야 할 뿐만 아니라 이동도해야합니다 .

다행스럽게도 우리에게는 쉽습니다.

class dumb_array
{
public:
    // ...

    // move constructor
    dumb_array(dumb_array&& other)
        : dumb_array() // initialize via default constructor, C++11 only
    {
        swap(*this, other);
    }

    // ...
};

무슨 일 이니? 이동 건설의 목표 : 클래스의 다른 인스턴스에서 리소스를 가져 와서 할당 가능하고 파괴 가능하도록 보장 된 상태로 둡니다.

그래서 우리가 한 일은 간단합니다. 기본 생성자 (C ++ 11 기능)를 통해 초기화 한 다음 other 교체합니다. 클래스의 기본 생성 된 인스턴스가 안전하게 할당되고 소멸 될 수 있다는 것을 알기 때문에 스와핑 후에도 other 클래스가 동일하게 수행 할 수 있습니다.

일부 컴파일러는 생성자 위임을 지원하지 않으므로이 경우 수동으로 클래스를 생성해야합니다. 이는 불행하지만 다행히도 사소한 작업입니다.

왜 그게 효과가 있니?

그것은 우리가 수업에해야 할 유일한 변화입니다. 그렇다면 왜 효과가 있습니까? 매개 변수를 참조가 아닌 값으로 만들기 위해 우리가 결정한 중요한 결정을 기억하십시오.

dumb_array& operator=(dumb_array other); // (1)

이제, 만약 other 값이 rvalue로 초기화된다면, 그것은 움직일 것 입니다. 완전한. 같은 방식으로 C ++ 03은 인수에 의한 인수를 취하여 복사 생성자 기능을 다시 사용하게하고 C ++ 11은 적절할 때 자동으로 이동 생성자를 선택합니다. (물론 이전에 링크 된 기사에서 언급했듯이 값의 복사 / 이동은 모두 생략 할 수 있습니다.)

복사 및 스왑 이디엄을 결론지었습니다.

각주

* 왜 mArray 를 null로 설정합니까? 연산자의 더 이상의 코드가 throw되면 dumb_array 의 소멸자가 dumb_array 될 수 있습니다. null로 설정하지 않고 이런 일이 발생하면 이미 삭제 된 메모리를 삭제하려고 시도합니다! null을 삭제하는 것은 아무런 작업이 아니므로 null로 설정하여이를 방지합니다.

† 우리 유형에 대해 std::swap 을 전문화해야하고, 클래스 내 swap 과 함께 자유 기능 swap 등을 제공해야한다는 다른 주장이 있습니다. 그러나 이것은 모두 불필요합니다 : swap 적절한 사용은 자격이없는 것입니다 전화를 걸면 우리의 기능은 ADL 통해 발견 될 것입니다. 한 가지 기능 만 수행합니다.

‡ 이유는 간단합니다. 일단 리소스를 소유하고 나면 언제 어디서나 스왑하거나 이동할 수 있습니다 (C ++ 11). 매개 변수 목록에서 사본을 작성하면 최적화가 최대화됩니다.




이미 좋은 해답이 있습니다. 나는 그들이 부족하다고 생각하는 것에 주로 초점을 맞출 것이다. 복사 - 스왑 (copy-and-swap) 관용구에 대한 "단점"에 대한 설명 ....

복사 및 스왑 이디엄이란 무엇입니까?

할당 연산자를 스왑 함수로 구현하는 방법은 다음과 같습니다.

X& operator=(X rhs)
{
    swap(rhs);
    return *this;
}

기본적인 아이디어는 다음과 같습니다.

  • 객체에 할당하는 가장 오류가 발생하기 쉬운 부분은 새로운 상태가 필요로하는 모든 자원 (예 : 메모리, 설명자)을 확보하는 것입니다.

  • 새로운 값의 사본이 만들어지면 객체의 현재 상태 (즉, *this ) 수정 하기 전에 획득을 시도 할 수 있습니다. 이것이 rhs참조가 아닌 (즉 복사 됨)으로 받아 들여지는 이유입니다

  • 로컬 복사본의 상태를 바꾸는 것이 가능합니다. *this 로컬 복사본은 나중에 특정 상태가 필요하지 않으므로 잠재적으로 실패 / 예외없이 비교적 쉽게 수행 할 수 있습니다. (소멸자가 실행될 상태가 필요합니다. > = C ++에서 옮겨 지는 객체 11)

언제 사용해야합니까? (어떤 문제가 해결 되나요?)

  • 할당 된 객체가 예외를 throw하는 할당에 영향을받지 않도록하려면 강력한 예외 보장이있는 swap 이 있거나 이상적으로는 스레딩 / throw 할 수없는 스 ..

  • 당신은 깨끗하고 이해하기 쉽고 (단순한) 복사 생성자, swap 및 소멸자 함수의 관점에서 할당 연산자를 정의하는 강력한 방법을 원할 때.

    • 복사 및 스왑으로 수행되는 자체 할당은 간과 할 수없는 최 소의 사례를 피합니다. ‡

  • 할당 중에 추가 임시 오브젝트를 작성하여 작성된 성능 저하 또는 순간적으로 높은 자원 사용이 어플리케이션에 중요하지 않은 경우. ⁂

swap 스레딩 : 객체가 포인터로 추적하는 데이터 멤버를 신뢰할 수있게 교환 할 수 있지만 스로우 프리 스왑이 없거나 스왑을 X tmp = lhs; lhs = rhs; rhs = tmp; 로 구현해야하는 포인터가 아닌 데이터 멤버를 X tmp = lhs; lhs = rhs; rhs = tmp; X tmp = lhs; lhs = rhs; rhs = tmp; 복사 - 구성 또는 할당이 던져 질 수도 있고, 스왑 된 일부 데이터 멤버와 그렇지 않은 데이터 멤버를 남겨 둘 가능성도 여전히있다. 이 잠재력은 C ++ 03 std::string 의 다른 답변에 대한 James의 주석에도 적용됩니다.

@wilhelmtell : C ++ 03에는 std :: string :: swap (std :: swap에 의해 호출 됨)에 의해 잠재적으로 throw되는 예외에 대한 언급이 없습니다. C ++ 0x에서 std :: string :: swap은 noexcept이며 예외를 throw해서는 안됩니다. - James McNellis Dec 22 '10 at 15:24

• 개별 객체에서 할당 할 때 쉽게 문제가 발생할 수있는 할당 연산자 구현은 자체 할당에 실패 할 수 있습니다. 클라이언트 코드가 자체 할당을 시도하는 것조차도 상상할 수없는 것처럼 보일 수도 있지만 x = f(x); 컨테이너에 대한 algo 연산 중에 상대적으로 쉽게 발생할 수 있습니다 x = f(x); code f 는 (아마도 #ifdef 브랜치에 대해서만) 매크로 ala #define f(x) x 또는 #define f(x) x 대한 참조를 반환하는 함수 또는 심지어 x = c1 ? x * 2 : c2 ? x / 2 : x; 과 같은 (비효율적이지만 간결한) 코드 x = c1 ? x * 2 : c2 ? x / 2 : x; x = c1 ? x * 2 : c2 ? x / 2 : x; ). 예 :

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

자체 할당시, 위의 코드는 x.p_;x.p_; , 새로 할당 된 힙 영역에서 p_p_ 다음 그 안에있는 초기화되지 않은 데이터를 읽으려고 시도합니다 (정의되지 않은 동작). 너무 이상한 일이 없다면 copy 는 모든 파괴 된 'T'에 자체 할당을 시도합니다!

복사 및 스왑 (copy-and-swap) 관용구는 여분의 임시 사용 (운영자 매개 변수가 복사 생성 된 경우)으로 인해 비 효율성 또는 제한을 초래할 수 있습니다.

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

여기서 손으로 작성한 Client::operator=rhs 와 동일한 서버에 *this 연결되어 있는지 확인할 수 있습니다 (유용한 경우 "재설정"코드를 전송하는 경우). 반면 카피 앤드 스왑 방식은 카피 앤드 스왑 방식을 호출합니다. 고유 한 소켓 연결을 연 다음 원래의 연결을 닫으려는 작성자가 될 생성자. 이것은 단순한 프로세스 내 변수 복사본 대신에 원격 네트워크 상호 작용을 의미 할뿐만 아니라 소켓 리소스 또는 연결에 대한 클라이언트 또는 서버 제한에 위배 될 수 있습니다. (물론이 클래스는 꽤 ​​무시 무시한 인터페이스를 가지고 있지만, 또 다른 문제입니다 .- P).




C ++에서 연산자 오버로딩의 세 가지 기본 규칙

C ++에서 연산자 오버로딩에 관해서는 세 가지 기본 규칙이 있습니다 . 이러한 모든 규칙과 마찬가지로 실제로 예외가 있습니다. 때로는 사람들이 그들로부터 벗어 났고 그 결과는 나쁜 코드가 아니었지만 그런 긍정적 인 편차는 거의 없었습니다. 최소한 내가 본 100 편 중 99 편은 정당화되지 않았습니다. 그러나 1000 년 중 999 회가되었을 수도 있습니다. 따라서 다음 규칙을 따르는 것이 좋습니다.

  1. 운영자의 의미가 명확하고 분명한 사실이 아니라면 과부하가되어서는 안됩니다. 대신 잘 선택된 이름으로 함수를 제공하십시오.
    기본적으로 연산자의 오버로드에 대한 가장 중요한 원칙은 다음과 같이 말합니다. 하지 마십시오 . 연산자 오버로딩에 대해 많이 알기 때문에 이상하게 보일 수 있습니다. 따라서 많은 기사, 책 장 및 기타 텍스트가이 모든 것을 처리합니다. 그러나이 명백한 증거에도 불구하고 운영자 과부하가 적절한 경우는 놀랍게도 거의 없습니다 . 그 이유는 실제로 응용 프로그램 도메인에서 연산자를 사용하는 것이 잘 알려져 있고 논쟁의 여지가 없다면 연산자의 응용 프로그램 뒤에있는 의미를 이해하기 어렵다는 것입니다. 대중적인 믿음과는 달리 이것은 거의 사실이 아닙니다.

  2. 항상 운영자의 잘 알려진 의미에 충실하십시오.
    C ++은 오버로드 된 연산자의 의미에 아무런 제한을 두지 않습니다. 컴파일러는 바이너리 + 연산자를 구현 한 코드를 기꺼이 받아 들여 올바른 피연산자에서 뺍니다. 그러나 그러한 연산자의 사용자는 a + ba + b 에서 빼기 a a + b 를 의심하지 않습니다. 물론 이것은 응용 프로그램 도메인에서 연산자의 의미가 확실하지 않다고 가정합니다.

  3. 항상 관련된 모든 작업을 제공하십시오.
    운영자는 서로 및 다른 작업과 관련이 있습니다. 유형 a + b 지원 a + b 사용자는 a += b 도 호출 할 수 있습니다. 접두사 증분 ++a 지원 a++ 가 잘 작동 할 것으로 기대됩니다. 그들이 a < b 인지 여부를 확인할 수 있다면, a > b 인지 여부를 확인할 수 있어야합니다. 사용자가 유형을 복사 할 수있는 경우 할당이 올바르게 작동 할 것으로 예상됩니다.

회원과 비회원 간의 결정을 계속하십시오.







c++ copy-constructor assignment-operator c++-faq copy-and-swap