이동 할당 연산자 및`if (this! = & rhs)`


126

클래스의 할당 연산자에서 일반적으로 할당되는 객체가 호출 객체인지 확인해야하므로 문제가 발생하지 않습니다.

Class& Class::operator=(const Class& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

이동 할당 연산자에 대해 동일한 것이 필요합니까? this == &rhs사실이 될 상황이 있습니까?

? Class::operator=(Class&& rhs) {
    ?
}

12
질문되는 Q와 관련이 없으며 타임 라인에서이 Q를 읽은 신규 사용자 (Seth가 이미 알고 있음)가 잘못된 아이디어를 얻지 않도록 Copy and Swap 은 복사 할당 연산자를 구현하는 올바른 방법입니다. 자체 할당 등을 확인할 필요가 없습니다.
Alok Save

5
@VaughnCato : A a; a = std::move(a);.
Xeo

11
@VaughnCato 사용 std::move은 정상입니다. 그런 다음 앨리어싱을 고려하고 호출 스택의 깊숙한 곳에 있고에 대한 참조가 하나 T있고 다른 참조가 T...에 대한 참조가 있을 때 여기에서 ID를 확인 하시겠습니까? 동일한 인수를 두 번 전달할 수 없음을 문서화하면 해당 두 참조가 별칭이 아님을 정적으로 증명할 첫 번째 호출 (또는 호출)을 찾고 싶습니까? 아니면 자기 할당이 제대로 작동하도록 하시겠습니까?
Luc Danton 2012

2
@LucDanton 할당 연산자에서 어설 션을 선호합니다. std :: move가 rvalue 자체 할당으로 끝날 수있는 방식으로 사용 된 경우 수정해야 할 버그라고 생각합니다.
Vaughn Cato

4
@VaughnCato 셀프 스왑이 정상적인 한 곳은 std::sort또는 내부에 있습니다 . 먼저 확인하지 않고 배열 std::shuffleith와 jth 요소를 스왑 할 때마다 i != j. ( std::swap이동 할당 측면에서 구현됩니다.)
Quuxplusone 2014

답변:


143

와, 여기 청소할 게 너무 많아 ...

첫째, 복사 및 교체 가 항상 복사 할당을 구현하는 올바른 방법은 아닙니다. 의 경우 거의 확실하게 dumb_array이것은 차선책입니다.

사용 복사 및 스왑 입니다 dumb_array아래 계층에서 최대한의 기능을 가장 비용이 많이 드는 작업을두기의 고전적인 예이다. 모든 기능을 원하고 성능 저하를 기꺼이 지불하려는 고객에게 적합합니다. 그들은 그들이 원하는 것을 정확하게 얻습니다.

그러나 완전한 기능이 필요하지 않고 대신 최고의 성능을 찾는 클라이언트에게는 재앙입니다. 그들에게 dumb_array너무 느리기 때문에 그들이 다시 작성해야 소프트웨어의 또 다른 작품이다. dumb_array다르게 설계 되었 더라면 어느 클라이언트도 타협하지 않고 두 클라이언트 모두 만족할 수있었습니다.

두 클라이언트를 모두 만족시키는 핵심은 가장 낮은 수준에서 가장 빠른 작업을 구축 한 다음 더 많은 비용으로 더 완전한 기능을 위해 API를 추가하는 것입니다. 즉, 강력한 예외 보장이 필요합니다. 필요 없어? 더 빠른 솔루션이 있습니다.

구체적으로 살펴 보겠습니다. 다음은 다음에 대한 빠르고 기본적인 예외 보장 복사 할당 연산자입니다 dumb_array.

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other)
    {
        if (mSize != other.mSize)
        {
            delete [] mArray;
            mArray = nullptr;
            mArray = other.mSize ? new int[other.mSize] : nullptr;
            mSize = other.mSize;
        }
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}

설명:

최신 하드웨어에서 할 수있는 더 비싼 일 중 하나는 힙으로 여행하는 것입니다. 힙으로의 여행을 피하기 위해 할 수있는 모든 것은 시간과 노력입니다. 의 클라이언트는 dumb_array종종 동일한 크기의 어레이를 할당하기를 원할 수 있습니다. 그리고 그들이 할 때, 당신이해야 할 일은 memcpy(아래에 숨겨져 있음 std::copy)입니다. 동일한 크기의 새 배열을 할당 한 다음 동일한 크기의 이전 배열을 할당 해제하고 싶지는 않습니다!

이제 강력한 예외 안전을 원하는 고객을 위해 :

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    swap(lhs, rhs);
    return lhs;
}

또는 C ++ 11에서 이동 할당을 활용하려면 다음과 같아야합니다.

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    lhs = std::move(rhs);
    return lhs;
}

경우 dumb_array의 고객 가치 속도, 그들은을 호출해야합니다 operator=. 강력한 예외 안전성이 필요한 경우 다양한 개체에서 작동하고 한 번만 구현하면되는 호출 할 수있는 일반 알고리즘이 있습니다.

이제 원래 질문으로 돌아갑니다 (이 시점에서 type-o가 있음).

Class&
Class::operator=(Class&& rhs)
{
    if (this == &rhs)  // is this check needed?
    {
       // ...
    }
    return *this;
}

이것은 실제로 논란의 여지가있는 질문입니다. 어떤 사람들은 예라고 말할 것이고, 어떤 사람들은 아니오라고 말할 것입니다.

제 개인적인 의견은 아니오입니다.이 수표는 필요하지 않습니다.

이론적 해석:

객체가 rvalue 참조에 바인딩되면 다음 두 가지 중 하나입니다.

  1. 일시적입니다.
  2. 발신자가 믿길 바라는 물건은 일시적인 것입니다.

실제 임시 객체에 대한 참조가있는 경우 정의에 따라 해당 객체에 대한 고유 한 참조가 있습니다. 전체 프로그램의 다른 곳에서는 참조 할 수 없습니다. 즉 this == &temporary 불가능합니다 .

이제 고객이 거짓말을했고, 그렇지 않을 때 일시적인 서비스를받을 것이라고 약속했다면, 당신이 신경 쓸 필요가 없는지 확인하는 것은 고객의 책임입니다. 정말 조심하고 싶다면 이것이 더 나은 구현이 될 것이라고 믿습니다.

Class&
Class::operator=(Class&& other)
{
    assert(this != &other);
    // ...
    return *this;
}

즉 , 자체 참조 전달 되면 수정해야하는 클라이언트 부분의 버그입니다.

완전성을 위해 다음은에 대한 이동 할당 연산자입니다 dumb_array.

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

이동 할당의 일반적인 사용 사례에서 이동 *this된 개체 delete [] mArray;가 될 것이므로 작동 하지 않아야합니다. 구현시 가능한 한 빨리 nullptr에서 삭제하는 것이 중요합니다.

경고:

어떤 사람들은 그것이 swap(x, x)좋은 생각이거나 단지 필요한 악 이라고 주장 할 것 입니다. 그리고 이것은 스왑이 기본 스왑으로 이동하면 자체 이동 할당이 발생할 수 있습니다.

나는 그 반대 swap(x, x)입니다 지금까지 좋은 아이디어입니다. 내 코드에서 발견되면 성능 버그로 간주하여 수정하겠습니다. 그러나 허용하려는 경우 swap(x, x)이동 된 값에 대해서만 self-move-assignemnet이 수행 된다는 점을 인식하십시오 . 그리고 우리의 dumb_array예에서 이것은 단순히 assert를 생략하거나 이동 된 케이스로 제한한다면 완전히 무해 할 것입니다 :

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other || mSize == 0);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

두 개의 이동 된 (빈)을 자체 할당 dumb_array하면 쓸모없는 명령을 프로그램에 삽입하는 것 외에는 잘못된 작업을 수행하지 않습니다. 대부분의 물체에 대해 이와 동일한 관찰을 할 수 있습니다.

<최신 정보>

나는이 문제에 대해 좀 더 생각하고 내 입장을 다소 바꿨다. 이제 할당이 자체 할당을 허용해야한다고 생각하지만 복사 할당과 이동 할당에 대한 게시 조건은 다릅니다.

복사 할당의 경우 :

x = y;

값을 y변경해서는 안되는 사후 조건이 있어야합니다. 때 &x == &y이 사후가로 변환 후 : 자기 복사 할당의 가치에 영향이 없어야합니다 x.

이동 할당의 경우 :

x = std::move(y);

y유효하지만 지정되지 않은 상태 를 가진 사후 조건이 있어야합니다. &x == &y이 사후 조건이 다음과 같이 변환 되면 x유효하지만 지정되지 않은 상태가 있습니다. 즉, 자체 이동 할당은 아무 작업도 할 필요가 없습니다. 그러나 충돌해서는 안됩니다. 이 사후 조건은 swap(x, x)작업 만 허용 하는 것과 일치 합니다.

template <class T>
void
swap(T& x, T& y)
{
    // assume &x == &y
    T tmp(std::move(x));
    // x and y now have a valid but unspecified state
    x = std::move(y);
    // x and y still have a valid but unspecified state
    y = std::move(tmp);
    // x and y have the value of tmp, which is the value they had on entry
}

위의 내용은 x = std::move(x)충돌하지 않는 한 작동합니다. x유효하지만 지정되지 않은 상태로 둘 수 있습니다 .

dumb_array이를 달성하기 위해 이동 할당 연산자를 프로그래밍하는 세 가지 방법 이 있습니다.

dumb_array& operator=(dumb_array&& other)
{
    delete [] mArray;
    // set *this to a valid state before continuing
    mSize = 0;
    mArray = nullptr;
    // *this is now in a valid state, continue with move assignment
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

위의 구현은 자기 할당을 관대하지만, *thisother자기 이동 할당 후 크기가 0 인 배열의 원래 값은 상관없이 끝나게 *this됩니다. 이건 괜찮아.

dumb_array& operator=(dumb_array&& other)
{
    if (this != &other)
    {
        delete [] mArray;
        mSize = other.mSize;
        mArray = other.mArray;
        other.mSize = 0;
        other.mArray = nullptr;
    }
    return *this;
}

위의 구현은 복사 할당 연산자가하는 것과 같은 방식으로 자체 할당을 허용합니다. 이것도 괜찮습니다.

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

위의 내용은 dumb_array"즉시"폐기해야하는 리소스가없는 경우에만 괜찮습니다 . 예를 들어 유일한 리소스가 메모리 인 경우 위의 내용은 괜찮습니다. dumb_array뮤텍스 잠금 또는 파일의 열린 상태를 유지할 수있는 경우 클라이언트는 이동 할당의 lhs에있는 해당 리소스가 즉시 해제 될 것으로 합리적으로 예상 할 수 있으므로이 구현이 문제가 될 수 있습니다.

첫 번째 비용은 두 개의 추가 상점입니다. 두 번째 비용은 테스트 및 분기입니다. 둘 다 작동합니다. 둘 다 C ++ 11 표준의 표 22 MoveAssignable 요구 사항의 모든 요구 사항을 충족합니다. 세 번째는 비 메모리 리소스 문제를 모듈로도 작동합니다.

세 가지 구현 모두 하드웨어에 따라 비용이 다를 수 있습니다. 지점 비용은 얼마입니까? 레지스터가 많거나 거의 없습니까?

요점은 자체 복사 할당과 달리 자체 이동 할당이 현재 값을 유지할 필요가 없다는 것입니다.

</최신 정보>

Luc Danton의 의견에서 영감을 얻은 마지막 (희망적으로) 편집 :

메모리를 직접 관리하지 않는 고급 클래스를 작성하는 경우 (하지만이를 수행하는 기본 또는 멤버가있을 수 있음) 이동 할당의 최상의 구현은 다음과 같습니다.

Class& operator=(Class&&) = default;

이것은 각 기지와 각 구성원을 차례로 할당하며 this != &other수표를 포함하지 않습니다 . 이것은 당신의 기지와 회원들 사이에 불변성을 유지할 필요가 없다고 가정 할 때 가장 높은 성능과 기본적인 예외 안전을 제공 할 것입니다. 강력한 예외 안전을 요구하는 고객의 경우 strong_assign.


6
이 답변에 대해 어떻게 생각하는지 모르겠습니다. 메모리를 매우 명시 적으로 관리하는 이러한 클래스를 구현하는 것이 일반적인 작업 인 것처럼 보이게합니다. 그것은 당신이 때 사실 쓰기와 같은 클래스와 인터페이스가 간결하고 편리하도록 매우 매우 예외 안전 보장에 대한 신중하고 달콤한 자리를 찾는 수있다, 그러나 문제는 일반적인 조언을 요청하는 것으로 보인다.
Luc Danton

예, 리소스와 사물을 관리하는 수업에 시간 낭비이기 때문에 복사 및 교체를 절대 사용하지 않습니다 (왜 모든 데이터의 또 다른 전체 사본을 만들까요?). 감사합니다. 제 질문에 대한 답변입니다.
Seth Carnegie

5
이동 할당-부터 자기가해야한다는 제안에 대한을 downvoted 주장-실패하거나 "지정되지 않은"결과를 생성합니다. 스스로 할당하는 것은 말 그대로 가장 쉬운 경우 입니다. 클래스가에서 충돌하는 경우 std::swap(x,x)더 복잡한 작업을 올바르게 처리 할 수 ​​있다고 믿어야하는 이유는 무엇입니까?
Quuxplusone 2014

1
@Quuxplusone : 내 답변에 대한 업데이트에서 언급했듯이 assert-fail에 대해 귀하와 동의했습니다. std::swap(x,x)가면 다음과 같은 경우에도 작동합니다.x = std::move(x) 지정되지 않은 결과를 생성합니다. 시도 해봐! 나를 믿을 필요는 없습니다.
Howard Hinnant 2014

@HowardHinnant 좋은 점 swap은 이동 가능한 상태 로 x = move(x)떠나는 한 작동합니다 x. 그리고 std::copy/ std::move알고리즘은 이미 작동하지 않는 복사본에 대해 정의되지 않은 동작을 생성하도록 정의되어 있습니다 (아야, 20 세memmove 의 경우 사소한 경우가 맞지만 std::move그렇지 않습니다!). 그래서 나는 아직 자기 할당을위한 "슬램 덩크"를 생각하지 않은 것 같다. 그러나 분명히 자체 할당은 표준이 축복했는지 여부에 관계없이 실제 코드에서 많이 발생합니다.
Quuxplusone 2014

11

첫째, 이동 할당 연산자의 서명이 잘못되었습니다. 이동은 소스 개체에서 리소스를 훔치기 때문에 소스는 비 constr 값 참조 여야합니다 .

Class &Class::operator=( Class &&rhs ) {
    //...
    return *this;
}

여전히 (비 const) l 값 참조 를 통해 반환 합니다.

두 가지 유형의 직접 할당에 대해 표준은 자체 할당을 확인하는 것이 아니라 자체 할당이 충돌 및 화상을 유발하지 않는지 확인하는 것입니다. 일반적으로, 아무도 명시 적으로하지 않습니다 x = x또는 y = std::move(y)이어질 수 있습니다, 전화 있지만, 특히 다양한 기능을 통해, 앨리어싱 a = b또는 c = std::move(d)자기 과제를 존재하게. 자체 할당에 대한 명시적인 검사, 즉 this == &rhstrue 일 때 기능의 핵심을 건너 뛰는 것은 자체 할당 안전을 보장하는 한 가지 방법입니다. 그러나 이것은 가장 일반적인 경우 (분기 및 캐시 미스로 인해)에 대한 최적화 방지 인 반면 (희망적으로) 드문 경우를 최적화하기 때문에 최악의 방법 중 하나입니다.

이제 (적어도) 피연산자 중 하나가 직접 임시 개체 인 경우 자체 할당 시나리오를 가질 수 없습니다. 어떤 사람들은 그 가정을 옹호하고 그 가정이 잘못되었을 때 코드가 어리석게 될 정도로 코드를 최적화합니다. 사용자에게 동일한 객체 검사를 덤핑하는 것은 무책임하다고 말합니다. 우리는 복사 할당에 대한 주장을하지 않습니다. 이동 할당의 입장을 바꾸는 이유는 무엇입니까?

다른 응답자에서 변경 한 예를 만들어 보겠습니다.

dumb_array& dumb_array::operator=(const dumb_array& other)
{
    if (mSize != other.mSize)
    {
        delete [] mArray;
        mArray = nullptr;  // clear this...
        mSize = 0u;        // ...and this in case the next line throws
        mArray = other.mSize ? new int[other.mSize] : nullptr;
        mSize = other.mSize;
    }
    std::copy(other.mArray, other.mArray + mSize, mArray);
    return *this;
}

이 복사 할당은 명시적인 검사없이 자체 할당을 정상적으로 처리합니다. 소스 및 대상 크기가 다른 경우 할당 해제 및 재 할당이 복사보다 우선합니다. 그렇지 않으면 복사 만 완료됩니다. 자체 할당은 최적화 된 경로를 얻지 못하며 소스 및 대상 크기가 동일하게 시작될 때와 동일한 경로로 덤프됩니다. 두 개체가 동일한 경우 (동일한 개체 인 경우 포함) 복사는 기술적으로 불필요하지만, 해당 수표 자체가 가장 낭비이기 때문에 동등성 검사 (가치 또는 주소)를 수행하지 않을 때의 대가입니다. 그 시간의. 여기서 객체 자체 할당은 일련의 요소 수준 자체 할당을 유발합니다. 요소 유형은이 작업을 수행하기에 안전해야합니다.

소스 예제와 마찬가지로이 복사 할당은 기본적인 예외 안전 보장을 제공합니다. 강력한 보장을 원하면 복사 및 이동 할당을 모두 처리 하는 원본 복사 및 교체 쿼리 의 통합 할당 연산자를 사용합니다 . 그러나이 예의 요점은 속도를 높이기 위해 안전성을 한 단계 낮추는 것입니다. (BTW, 우리는 개별 요소의 값이 독립적이라고 가정하고 일부 값을 다른 값과 비교하여 제한하는 불변 제약이 없다고 가정합니다.)

이 동일한 유형에 대한 이동 할당을 살펴 보겠습니다.

class dumb_array
{
    //...
    void swap(dumb_array& other) noexcept
    {
        // Just in case we add UDT members later
        using std::swap;

        // both members are built-in types -> never throw
        swap( this->mArray, other.mArray );
        swap( this->mSize, other.mSize );
    }

    dumb_array& operator=(dumb_array&& other) noexcept
    {
        this->swap( other );
        return *this;
    }
    //...
};

void  swap( dumb_array &l, dumb_array &r ) noexcept  { l.swap( r ); }

사용자 지정이 필요한 스왑 가능한 형식 swap에는 형식과 동일한 네임 스페이스에서 호출되는 두 인수가없는 함수 가 있어야합니다. (네임 스페이스 제한으로 인해 스왑에 대한 정규화되지 않은 호출이 작동하도록 허용합니다.) 컨테이너 유형은 swap표준 컨테이너와 일치 하는 공용 멤버 함수 도 추가해야합니다 . 멤버 swap가 제공되지 않으면 free-function swap은 스왑 가능한 유형의 동반자로 표시되어야합니다. 를 사용하도록 이동을 사용자 정의하는 경우 swap고유 한 스왑 코드를 제공해야합니다. 표준 코드는 유형의 이동 코드를 호출하므로 이동 사용자 정의 유형에 대해 무한 상호 재귀가 발생합니다.

소멸자와 마찬가지로 스왑 함수와 이동 작업은 가능하면 절대로 던져서는 안되며 아마도 그렇게 표시되어야합니다 (C ++ 11에서). 표준 라이브러리 유형 및 루틴에는 던질 수없는 이동 유형에 대한 최적화가 있습니다.

이 첫 번째 버전의 이동 할당은 기본 계약을 이행합니다. 소스의 리소스 마커가 대상 개체로 전송됩니다. 이제 소스 개체가 관리하므로 이전 리소스가 유출되지 않습니다. 그리고 소스 객체는 할당 및 소멸을 포함한 추가 작업을 적용 할 수있는 사용 가능한 상태로 남아 있습니다.

이 이동 할당은 자체 할당에 대해 자동으로 안전합니다. swap 통화 . 또한 매우 예외적으로 안전합니다. 문제는 불필요한 자원 보유입니다. 대상에 대한 이전 리소스는 개념적으로 더 이상 필요하지 않지만 여기에서는 소스 개체가 유효한 상태로 유지 될 수 있도록 여전히 주변에 있습니다. 예정된 소스 객체의 파괴가 먼 길이면 자원 공간을 낭비하거나 총 자원 공간이 제한되고 (새) 소스 객체가 공식적으로 죽기 전에 다른 자원 청원이 발생하면 더 나빠집니다.

이 문제는 이동 할당 중 자체 타겟팅과 관련하여 논란이되고있는 현재 전문가 조언을 야기했습니다. 느린 리소스없이 이동 할당을 작성하는 방법은 다음과 같습니다.

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        delete [] this->mArray;  // kill old resources
        this->mArray = other.mArray;
        this->mSize = other.mSize;
        other.mArray = nullptr;  // reset source
        other.mSize = 0u;
        return *this;
    }
    //...
};

원본은 기본 조건으로 재설정되고 이전 대상 리소스는 삭제됩니다. 자체 할당의 경우 현재 개체가 자살합니다. 주된 방법은 액션 코드를 if(this != &other)블록 으로 둘러싸 거나 나사를 조이고 클라이언트가assert(this != &other) 초기 라인을 (기분이 좋다면).

대안은 통합 할당없이 복사 할당을 강력하게 예외적으로 안전하게 만드는 방법을 연구하고이를 이동 할당에 적용하는 것입니다.

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        dumb_array  temp{ std::move(other) };

        this->swap( temp );
        return *this;
    }
    //...
};

otherthis구별되는, other받는 사람 이동에 의해 비워 temp및 숙박 그런 식으로. 그런 다음에서 원래 보유한 리소스를 가져 오는 동안 this이전 리소스를 잃게 temp됩니다 other. 그런 다음 오래된 자원 this이 죽을 때temp .

자기 할당이 발생하면 비우는 othertemp빈 상자 this뿐만 아니라. 그런 다음 대상 객체는 자원이 때 돌아 오기 tempthis스왑. temp주장 의 죽음은 빈 개체로, 실질적으로 작동하지 않아야합니다. this/other 객체는 자원을 유지합니다.

move-assignment는 move-construction과 swapping이있는 한 절대 던지지 않아야합니다. 자체 할당 중에도 안전을 유지하는 데 드는 비용은 할당 해제 호출로 인해 소실되어야하는 저수준 유형에 대한 몇 가지 추가 지침입니다.


delete두 번째 코드 블록을 호출하기 전에 할당 된 메모리가 있는지 확인해야 합니까?
user3728501 2015-08-25

3
두 번째 코드 샘플 인 자체 할당 검사가없는 복사 할당 연산자는 잘못되었습니다. std::copy소스 및 대상 범위가 겹치는 경우 정의되지 않은 동작이 발생합니다 (일치하는 경우 포함). C ++ 14 [alg.copy] / 3를 참조하십시오.
MM

6

나는 자체 할당 안전 연산자를 원하지만 .NET 구현에서 자체 할당 검사를 작성하고 싶지 않은 사람들의 캠프에 operator=있습니다. 사실 저는 구현하고 싶지도 않습니다.operator= , 기본 동작이 '즉각적으로'작동하기를 원합니다. 최고의 특별 회원은 무료로 오는 회원입니다.

즉, 표준에있는 MoveAssignable 요구 사항은 다음과 같이 설명됩니다 (17.6.3.1 템플릿 인수 요구 사항 [utility.arg.requirements], n3290).

표현식 반환 유형 반환 값 사후 조건
t = rv T & tt는 할당 전의 rv 값과 동일합니다.

자리 표시자는 " t[은] T 유형의 수정 가능한 lvalue입니다." 그리고 "rv 는 T; 유형의 rvalue입니다." 이는 표준 라이브러리의 템플릿에 대한 인수로 사용되는 유형에 대한 요구 사항이지만 표준의 다른 부분을 살펴보면 이동 할당에 대한 모든 요구 사항이 이와 유사하다는 것을 알 수 있습니다.

이것은 a = std::move(a)'안전'해야 함을 의미합니다 . 만약 당신이 필요로하는 것이 신원 테스트라면 (예를 들어 this != &other), 그것을 시도하십시오. 그렇지 않으면 당신은 당신의 물건을 넣을 수 없을 것입니다 std::vector! (MoveAssignable이 필요한 멤버 / 작업을 사용하지 않는 경우를 제외하고는 신경 쓰지 마십시오.) 이전 예제 a = std::move(a)에서 this == &other실제로 유지됩니다.


a = std::move(a)일하지 않으면 수업이 어떻게 작동하지 않는지 설명 할 수 있습니까 std::vector? 예?
Paul J. Lucas

@ PaulJ.Lucas Calling std::vector<T>::erase은 MoveAssignable 이 아니면 허용되지 않습니다 T. (IIRC를 제외하고 일부 MoveAssignable 요구 사항이 C ++ 14 대신 MoveInsertable로 완화되었습니다.)
Luc Danton

좋아, 그래서 TMoveAssignable이되어야하지만, 왜 erase()요소를 그 자체로 옮기는 것에 의존 할까요?
Paul J. Lucas

@ PaulJ.Lucas 그 질문에 대한 만족스러운 대답은 없습니다. 모든 것은 '계약을 어 기지 말라'로 귀결됩니다.
Luc Danton 2015

2

현재 operator=함수가 작성 될 때 rvalue-reference 인수를 만들었 const으므로 포인터를 "훔치고"들어오는 rvalue 참조의 값을 변경할 수있는 방법이 없습니다. 단순히 변경할 수 없습니다. 그것에서만 읽을 수 있습니다. 일반적인 lvaue-reference 메서드 에서와 같이 객체 delete에서 포인터 등을 호출하기 시작하는 경우에만 문제가 발생 하지만 그런 종류의 rvalue-version의 요점을 this무너 뜨립니다 operator=. 즉, rvalue 버전을 사용하여 기본적으로 일반적으로 const-lvalue operator=메서드에 남아있는 동일한 작업을 수행하는 것은 중복되어 보입니다 .

이제 operator=constrvalue 참조 를 사용하도록 정의한 경우 확인이 필요한 유일한 방법 this은 임시가 아닌 rvalue 참조를 의도적으로 반환 한 함수에 개체를 전달하는 것입니다.

예를 들어, 누군가가 operator+함수 를 작성하려고 시도 하고 객체 유형에 대한 스택 추가 작업 중에 추가 임시가 생성되는 것을 "방지"하기 위해 rvalue 참조와 lvalue 참조를 혼합하여 활용 한다고 가정 합니다.

struct A; //defines operator=(A&& rhs) where it will "steal" the pointers
          //of rhs and set the original pointers of rhs to NULL

A&& operator+(A& rhs, A&& lhs)
{
    //...code

    return std::move(rhs);
}

A&& operator+(A&& rhs, A&&lhs)
{
    //...code

    return std::move(rhs);
}

int main()
{
    A a;

    a = (a + A()) + A(); //calls operator=(A&&) with reference bound to a

    //...rest of code
}

이제 내가 rvalue 참조에 대해 이해 한 바에 따르면 위의 작업은 권장하지 않습니다 (즉, rvalue 참조가 아닌 임시로 반환해야 함).하지만 누군가 여전히 그렇게하려면 확인하고 싶을 것입니다. 들어오는 rvalue-reference가 this포인터 와 동일한 객체를 참조하지 않았는지 확인하십시오 .


"a = std :: move (a)"는이 상황을 다루는 간단한 방법입니다. 당신의 대답은 유효합니다.
Vaughn Cato 2012

1
대부분의 사람들이 의도적으로 그렇게하지 않을 것이라고 생각하지만, 이것이 가장 간단한 방법이라는 데 전적으로 동의합니다. :-) ... rvalue-reference가 const이면 읽기만 가능하므로 유일한 필요는 확인은 스와핑 스타일 작업 (즉, 깊은 복사본을 만드는 대신 포인터를 훔치는 등)이 아닌 일반적인 방법 에서 operator=(const T&&)수행하는 것과 동일한 다시 초기화를 수행 하기로 결정한 경우 입니다. thisoperator=(const T&)
Jason

1

내 대답은 여전히 ​​이동 할당이 자기 할당에 대해 저장 될 필요는 없지만 다른 설명이 있다는 것입니다. std :: unique_ptr을 고려하십시오. 하나를 구현하려면 다음과 같이 할 것입니다.

unique_ptr& operator=(unique_ptr&& x) {
  delete ptr_;
  ptr_ = x.ptr_;
  x.ptr_ = nullptr;
  return *this;
}

이것을 설명하는 Scott Meyers 를 보면 비슷한 일을합니다. (당신이 왜 스왑을하지 않는지 방황한다면-하나의 추가 쓰기가 있습니다). 그리고 이것은 자기 할당에 안전하지 않습니다.

때때로 이것은 불행한 일입니다. 모든 짝수를 벡터 밖으로 이동하는 것을 고려하십시오.

src.erase(
  std::partition_copy(src.begin(), src.end(),
                      src.begin(),
                      std::back_inserter(even),
                      [](int num) { return num % 2; }
                      ).first,
  src.end());

이것은 정수에 대해서는 괜찮지 만 이동 의미론으로 이와 같은 작업을 할 수 있다고 생각하지 않습니다.

결론적으로 객체 자체로 할당을 이동하는 것은 좋지 않으며 조심해야합니다.

작은 업데이트.

  1. 나는 나쁜 생각 인 Howard의 의견에 동의하지 않습니다. 그러나 여전히 "움직이는"개체의 자체 이동 할당이 작동 swap(x, x)해야 하므로 작동해야한다고 생각합니다. 알고리즘은 이러한 것들을 좋아합니다! 코너 케이스가 작동하면 항상 좋습니다. (그리고 나는 그것이 무료가 아닌 경우를 아직 보지 못했습니다. 그것이 존재하지 않는다는 의미는 아닙니다).
  2. 이것이 libc ++에서 unique_ptrs 할당이 구현되는 방법 unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); ...} 입니다. 자체 이동 할당에 안전합니다.
  3. 핵심 지침 은 자체 이동 할당이 괜찮을 것이라고 생각합니다.

0

(이 == rhs) 내가 생각할 수있는 상황이 있습니다. 이 문장의 경우 : Myclass obj; std :: move (obj) = std :: move (obj)


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