복사 및 교환 관용구는 무엇입니까?


2000

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

많은 곳에서 언급되었지만, "무엇입니까"라는 질문과 대답은 없었으므로 여기에 있습니다. 다음은 이전에 언급 한 장소의 일부 목록입니다.


7
gotw.ca/gotw/059.htm 허브 셔터에서
DumbCoder

2
놀랍게도, 나는이 질문을 내 대답 과 연관 시켜 의미론을 움직 였다.
fredoverflow

4
이 관용구에 대한 본격적인 설명을하는 것이 좋습니다. 모든 사람이 이에 대해 알아야합니다.
Matthieu M.

16
경고 : 복사 / 스왑 관용구가 유용한 것보다 훨씬 자주 사용됩니다. 복사본 할당에서 강력한 예외 안전 보장이 필요하지 않은 경우 성능에 해로운 경우가 많습니다. 또한 복사 할당에 강력한 예외 안전성이 필요한 경우 훨씬 빠른 복사 할당 연산자와 함께 짧은 일반 기능으로 쉽게 제공됩니다. slideshare.net/ripplelabs/howard-hinnant-accu2014 슬라이드 43-53을 참조하십시오 . 요약 : 복사 / 스왑은 도구 상자에 유용한 도구입니다. 그러나 그것은 과도하게 판매되었으며 그 결과 종종 남용되었습니다.
Howard Hinnant

2
@HowardHinnant : 그래, +1. 나는 거의 모든 C ++ 질문이 "복사 할 때 클래스가 충돌하는 것을 돕습니다"라는 시점에 이것을 썼으며 이것이 내 응답이었습니다. 복사 / 이동 시맨틱 작업이나 다른 작업을 원할 때 적절하지만 실제로는 최적이 아닙니다. 도움이 될 것이라고 생각되면 내 답변 맨 위에 면책 조항을 자유롭게 넣으십시오.
GManNickG

답변:


2183

개요

복사 및 교체 관용구가 필요한 이유는 무엇입니까?

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

복사 및 스왑 관용구 의 해결책이 우아 두 가지를 달성 대입 연산자를 지원합니다 : 피 코드 중복을 , 그리고 제공하는 강력한 예외 보증을 .

어떻게 작동합니까?

개념적으로 복사 생성자의 기능을 사용하여 데이터의 로컬 복사본을 만든 다음 swap함수로 복사 된 데이터를 가져 와서 이전 데이터를 새 데이터로 교체합니다. 그런 다음 임시 사본은 오래된 데이터를 가져 와서 폐기합니다. 새 데이터의 사본이 남았습니다.

복사 및 스왑 이디엄을 사용하려면 작업중인 복사 생성자, 작업중인 소멸자 (둘 다 래퍼의 기초이므로 어쨌든 완료해야 함)와 swap함수의 세 가지가 필요 합니다.

스왑 함수는 멤버의 멤버 인 클래스의 두 개체를 스왑 하는 비 투사 함수입니다. 우리는 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함수. 3의 규칙은 우리의 복사 생성자, 할당 연산자 및 소멸자의 존재를 성공적으로 수반하지만 실제로는 "The 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;
}

그리고 그게 다야! 한 번의 실패로 세 가지 문제는 모두 한 번에 우아하게 해결됩니다.

왜 작동합니까?

우리는 먼저 중요한 선택을 알게되었습니다 : 매개 변수 인수는 by-value 입니다. 다음과 같은 작업을 쉽게 수행 할 수 있지만 실제로는 많은 순진한 관용구 구현이 가능합니다.

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) noexcept ††
        : dumb_array() // initialize via default constructor, C++11 only
    {
        swap(*this, other);
    }

    // ...
};

무슨 일이야? 이동 구성의 목표를 기억하십시오. 클래스의 다른 인스턴스에서 자원을 가져 와서 할당 가능하고 파괴 가능한 상태로 유지하십시오.

우리가 한 일은 간단합니다. 기본 생성자 (C ++ 11 기능)를 통해 초기화 한 다음 other; 우리는 클래스의 기본 생성 인스턴스가 안전하게 할당되고 소멸 other될 수 있다는 것을 알고 있으므로 스왑 후에도 동일한 작업을 수행 할 수 있습니다.

(일부 컴파일러는 생성자 위임을 지원하지 않습니다.이 경우 클래스를 수동으로 기본 구성해야합니다. 불행히도 운이 좋지 않은 작업입니다.)

왜 작동합니까?

그것이 우리 수업에 필요한 유일한 변화이므로 왜 효과가 있습니까? 매개 변수를 참조가 아닌 값으로 만들기 위해 항상 결정한 사항을 기억하십시오.

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

이제 otherrvalue로 초기화되는 경우 이동 구성 됩니다. 완전한. C ++ 03과 같은 방식으로 인수 값을 사용하여 복사 생성자 기능을 재사용 할 수 있습니다. C ++ 11은 적절한 경우 이동 생성자를 자동으로 선택합니다. (물론, 이전에 링크 된 기사에서 언급했듯이, 가치의 복사 / 이동은 단순히 제거 될 수 있습니다.)

그리고 복사 및 스왑 숙어를 마칩니다.


각주

* 왜 mArraynull로 설정 합니까? 연산자의 추가 코드가 발생하면 소멸자 dumb_array가 호출 될 수 있습니다. 그리고 null로 설정하지 않고 발생하면 이미 삭제 된 메모리를 삭제하려고 시도합니다! null을 삭제하면 작동하지 않으므로 null로 설정하여이를 방지합니다.

†이 우리가 전문 것을 청구에있는 std::swap우리의 유형은 수준의 제공 swap에 따른 측 무료 기능 swap등,하지만이 모든 불필요한입니다 :의 적절한 사용이 swap지정되지 않은 호출을 통해 될 것이며, 우리의 기능이있을 것 ADL을 통해 발견 . 하나의 기능이 수행됩니다.

‡ 이유는 간단합니다. 일단 리소스를 확보 한 후에는 필요할 때마다 리소스를 교환 및 / 또는 이동할 수 있습니다 (C ++ 11). 그리고 매개 변수 목록에 사본을 작성하여 최적화를 최대화합니다.

†† 이동 생성자는 일반적으로이어야합니다 noexcept. 그렇지 않으면 std::vector이동이 의미가있는 경우에도 일부 코드 (예 : 크기 조정 논리)가 복사 생성자를 사용합니다. 물론 내부 코드에서 예외가 발생하지 않는 경우에만 예외로 표시하십시오.


17
@ GMan : 한 번에 여러 리소스를 관리하는 클래스가 실패 할 것으로 예상됩니다 (예외 안전은 악몽이됩니다). 클래스가 하나의 리소스를 관리하거나 비즈니스 기능이 있고 관리자를 사용하는 것이 좋습니다.
Matthieu M.

22
스왑 방법이 왜 친구로 선언되어 있는지 모르겠습니다.
szx

9
@asd : ADL을 통해 찾을 수 있습니다.
GManNickG

8
@neuviemeporte : 괄호를 사용하여 배열 요소가 기본적으로 초기화됩니다. 없으면 초기화되지 않습니다. 복사 생성자에서 값을 덮어 쓰므로 초기화를 건너 뛸 수 있습니다.
GManNickG

10
@neuviemeporte : swapADL이 boost::swap다른 스왑 인스턴스와 같이 가장 일반적인 코드에서 작동하도록하려면 ADL 중에 발견 해야합니다 . 스왑은 C ++에서 까다로운 문제이며 일반적으로 단일 액세스 지점이 (일관성) 최선이며 일반적으로 그렇게하는 유일한 방법은 무료 기능입니다 ( int스왑 멤버를 가질 수 없음, 예를 들어). 배경에 대한 내 질문 을 참조하십시오 .
GManNickG

274

: 할당, 그 중심에, 두 단계 인 객체의 이전 상태를 찢어 하고 사본으로 새로운 상태를 만드는 다른 객체의 상태.

기본적으로 이것이 소멸자복사 생성자 가하는 일이므로 첫 번째 아이디어는 작업을 위임하는 것입니다. 그러나, 파괴가 반드시 실패해서는 안되므로, 건설은 가능하지만, 실제로는 다른 방법으로하고 싶습니다 : 먼저 건설적인 부분을 수행 하고, 성공한 경우 에는 파괴적인 부분을 수행하십시오 . copy-and-swap 이디엄은이를 수행하는 방법입니다. 먼저 클래스의 복사 생성자를 호출하여 임시 객체를 만든 다음 해당 데이터를 임시 객체와 교체 한 다음 임시 소멸자가 이전 상태를 파괴하도록합니다.
이후swap()실패하지 않을 것으로 예상되며 실패 할 수있는 유일한 부분은 복사 구성입니다. 먼저 수행되고 실패하면 대상 객체에서 아무것도 변경되지 않습니다.

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

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

1
필체를 언급하는 것이 사본, 스왑 및 파괴를 언급하는 것만 큼 중요하다고 생각합니다. 스왑은 마술처럼 예외적으로 안전하지 않습니다. 스왑 포인터는 예외 안전하므로 예외 안전합니다. 당신은하지 않습니다 pimpl을 사용하지만, 그렇게하지 않으면 당신은 반드시 회원의 각 스왑은 예외 안전하다는 것을 확인해야합니다. 이 멤버들이 변할 수있을 때 그것은 악몽 일 수 있으며, 그들이 pimpl 뒤에 숨겨져 있으면 사소한 일입니다. 그런 다음 pimpl의 비용이 듭니다. 이는 종종 예외 안전이 성능 비용을 부담한다는 결론으로 ​​이어집니다.
wilhelmtell

7
std::swap(this_string, that)던지기 보증을 제공하지 않습니다. 강력한 예외 안전을 제공하지만, 무방비 보장은 아닙니다.
wilhelmtell

11
@wilhelmtell : C ++ 03에는 잠재적으로 발생할 수있는 예외 std::string::swap(에 의해 호출 됨 std::swap)에 대한 언급이 없습니다 . C ++ 0x에서 std::string::swapis는 noexcept예외를 발생시키지 않아야합니다.
James McNellis

2
@sbi @JamesMcNellis 알았지 만 요점은 여전히 ​​존재합니다. 클래스 유형의 멤버가있는 경우 스왑이 발생하지 않아야합니다. 포인터 인 단일 멤버가 있으면 사소합니다. 그렇지 않으면 그렇지 않습니다.
wilhelmtell

2
@wilhelmtell : 나는 그것이 교환의 요점이라고 생각했다 : 그것은 결코 던져지지 않으며 항상 O (1)입니다 (예, 알고 있습니다 std::array...)
sbi

44

이미 좋은 답변이 있습니다. 나는 그들이 부족하다고 생각하는 것에 주로 초점을 맞출 것입니다-복사 및 스왑 숙어를 가진 "단점"에 대한 설명 ....

복사 및 교환 관용구는 무엇입니까?

스왑 기능으로 할당 연산자를 구현하는 방법 :

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

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

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

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

  • 로컬 복사본의 상태를 교환 rhs하고 *this있다 일반적으로 로컬 복사본은 이후에 특정 상태를 필요로하지 않는다 주어 잠재적 인 장애 / 예외없이 비교적 쉽게 할 수있는 (단지 많이되는 물체에 관해서는, 실행에 소멸자에 대한 상태에 맞는을 필요로 이동 에서> = C ++ 11)

언제 사용해야합니까? (어떤 문제가 [/ create]를 해결 합니까?)

  • swap강력한 예외 보증 을 가지고 있거나 작성할 수 있다고 가정하고 예외를 던질 수있는 과제에 의해 과제 대상이 영향을받지 않도록하고 싶을 때 , 이상적으로 실패 할 수없는 경우 throw.. †

  • (더 간단한) 복사 생성자 swap및 소멸자 함수로 할당 연산자를 정의하는 깨끗하고 이해하기 쉽고 강력한 방법을 원할 때 .

    • 복사 및 스왑으로 수행되는 자체 할당 기능은 간과되는 간과를 피합니다. ‡

  • 할당 중에 추가 임시 개체를 생성하여 생성 된 성능 저하 또는 일시적으로 더 높은 리소스 사용이 응용 프로그램에 중요하지 않은 경우. ⁂

swap던지기 : 일반적으로 개체가 포인터로 추적하는 데이터 멤버를 안정적으로 스왑 할 수 있지만 스로우 프리 스왑이없는 포인터가 아닌 데이터 멤버는 스왑 X tmp = lhs; lhs = rhs; rhs = tmp;을 복사 및 구성 또는 할당 으로 구현해야 합니다. 던질 수 있지만 일부 데이터 멤버를 교체하지 않고 다른 멤버는 스왑하지 않은 상태로 남아있을 수 있습니다. 이 잠재력 std::string은 James가 다른 답변에 대해 언급 한 것처럼 C ++ 03에도 적용됩니다 .

@wilhelmtell : C ++ 03에는 std :: string :: swap (std :: swap에 의해 호출 됨)에서 발생할 수있는 예외에 대한 언급이 없습니다. C ++ 0x에서 std :: string :: swap은 예외가 아니며 예외를 발생시키지 않아야합니다. – James McNellis 12 월 22 일 10시 15 분 24 초


고유 한 객체에서 할당 할 때 제정신이 아닌 할당 연산자 구현은 자체 할당을 위해 쉽게 실패 할 수 있습니다. 이 클라이언트 코드도 자동 할당을 시도 할 것이라고 상상할 보일 수도 있지만,으로, 컨테이너에 너 한테 작업 중에 비교적 쉽게 일어날 수있는 x = f(x);곳 코드 f입니다 (아마도 일부에 대한 #ifdef지점) 매크로 ALA #define f(x) x나에 대한 참조를 반환하는 함수 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_;포인트 p_새로 할당 된 힙 지역에서는, 다음 시도는 읽기 초기화되지 않은 그 무엇도 이상하지 않을 경우, 내부 (정의되지 않은 동작) 데이터를 copy시도 모든 꺼야 단지 ..에 자체 할당 'T'를 파괴!


copy 복사 및 교체 관용구는 여분의 임시 사용으로 인해 비 효율성이나 제한 사항이 발생할 수 있습니다 (운영자 매개 변수가 복사 구성되어있는 경우).

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

여기서 손으로 쓴 것이 이미 동일한 서버에 연결되어 Client::operator=있는지 확인하고 (필요한 경우 "재설정"코드를 보내는 경우도 있음), 복사 및 스왑 방식은 복사 생성자를 호출하여 열도록 작성 될 수 있습니다. 별도의 소켓 연결 후 원래 연결을 닫습니다. 이는 단순한 in-process 변수 복사본 대신 원격 네트워크 상호 작용을 의미 할뿐만 아니라 소켓 리소스 또는 연결에 대한 클라이언트 또는 서버 제한을 ​​무시할 수 있습니다. (물론이 클래스는 꽤 ​​무시 무시한 인터페이스를 가지고 있지만 그것은 또 다른 문제입니다. ;-P).*thisrhs


4
즉, 소켓 연결은 단지 예일뿐입니다. 하드웨어 프로브 / 초기화 / 교정, 스레드 또는 난수 풀 생성, 특정 암호화 작업, 캐시, 파일 시스템 스캔, 데이터베이스와 같이 잠재적으로 고가의 초기화에 동일한 원칙이 적용됩니다. 연결 등.
Tony Delroy

하나의 (대규모) 단점이 있습니다. 현재의 사양에 따라 기술적 으로 객체 에는 이동 할당 연산자없습니다! 나중에 클래스의 멤버로 사용되면 새 클래스에 move-ctor가 자동 생성되지 않습니다! 출처 : youtu.be/mYrbivnruYw?t=43m14s
user362515

3
복사 할당 연산자의 주요 문제점 Client은 할당이 금지되지 않는다는 것입니다.
sbi

클라이언트 예제에서 클래스는 복사 할 수 없도록 만들어야합니다.
John Z. Li

25

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

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

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

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

}

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

여기에 이미지 설명을 입력하십시오

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


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

void swap(A& other) {

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

}

이번에는을 호출 swap하고 전달 other하면 컴파일러가 행복해집니다.

여기에 이미지 설명을 입력하십시오


결국, 2 개의 객체를 교환 하기 위해 함수를 사용할 필요 는 없습니다 friend. swap하나의 멤버 함수 를 만드는 것만큼이나 의미 가 있습니다.other 객체를 매개 변수로 갖는 좋습니다.

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


1
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg . 이것은 단순화 된 버전입니다. 매개 변수를 사용 하여 friend함수를 호출 할 때마다 오류가 발생하는 것 같습니다.*this
Oleksiy

1
내가 말했듯이 @GManNickG는 버그이며 다른 사람들에게 잘 작동 할 수 있습니다. 나는 단지 나와 같은 문제가있는 사람들을 돕고 싶었다. 내 수정, 비주얼 스튜디오 2012 익스프레스 2013 미리보기 및 멀리 이동 만든 유일한 것은 모두이 시도했다
올렉시

8
@GManNickG 모든 이미지와 코드 예제가있는 주석에는 맞지 않습니다. 사람들이 공감하면 괜찮습니다. 같은 버그를 겪고있는 누군가가있을 것입니다. 이 게시물의 정보는 필요한 것일 수도 있습니다.
Oleksiy

14
이것은 IDE 코드 강조 표시 (IntelliSense)의 버그 일뿐입니다 ... 경고 / 오류없이 잘 컴파일됩니다.
Amro

3
아직 VS 버그를 수정하지 않은 경우 여기에 VS 버그를보고하십시오. connect.microsoft.com/VisualStudio
Matt

15

C ++ 11 스타일 할당 자 인식 컨테이너를 다룰 때 경고 단어를 추가하고 싶습니다. 스와핑과 할당은 미묘하게 다른 의미를 가지고 있습니다.

구체성를 들어, 우리가 용기 생각해 보자 std::vector<T, A>, A어떤 상태 할당 타입을, 우리는 다음과 같은 기능을 비교한다 :

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

두 기능의 목적 fsfm제공하는 것입니다 a상태 b초기했다입니다. 하면 어떻게됩니까 그러나, 숨겨진 문제가있다 a.get_allocator() != b.get_allocator()? 대답은 다음과 같습니다. 작성합시다 AT = std::allocator_traits<A>.

  • 만약 AT::propagate_on_container_move_assignment 이며 std::true_type, 다음 fm의 할당 재 할당 a의 값을 b.get_allocator(), 그렇지 않으면하지 않는, 그리고 a원래의 할당을 계속 사용합니다. 이 경우 데이터 요소는 스토리지 ab호환되지 않기 때문에 개별적으로 스왑해야합니다 .

  • 경우 AT::propagate_on_container_swap이며 std::true_type, 다음 fs예상되는 방식으로 데이터와 할당자를 바꿉니다.

  • AT::propagate_on_container_swapis std::false_type인 경우 동적 검사가 필요합니다.

    • 인 경우 a.get_allocator() == b.get_allocator()두 컨테이너가 호환 가능한 스토리지를 사용하고 일반적인 방식으로 스와핑이 진행됩니다.
    • 그러나이면 a.get_allocator() != b.get_allocator()프로그램에 정의되지 않은 동작이 있습니다 ([container.requirements.general / 8] 참조).

결론은 컨테이너가 상태 저장 할당자를 지원하기 시작하자마자 스와핑이 C ++ 11에서 사소한 작업이되었습니다. 그것은 다소 "고급 사용 사례"이지만, 클래스가 리소스를 관리하고 메모리가 가장 인기있는 리소스 중 하나라면 이동 최적화가 일반적으로 흥미로워지기 때문에 전적으로 가능성이 낮지는 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.