와, 여기 청소할 게 너무 많아 ...
첫째, 복사 및 교체 가 항상 복사 할당을 구현하는 올바른 방법은 아닙니다. 의 경우 거의 확실하게 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 참조에 바인딩되면 다음 두 가지 중 하나입니다.
- 일시적입니다.
- 발신자가 믿길 바라는 물건은 일시적인 것입니다.
실제 임시 객체에 대한 참조가있는 경우 정의에 따라 해당 객체에 대한 고유 한 참조가 있습니다. 전체 프로그램의 다른 곳에서는 참조 할 수 없습니다. 즉 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;
}
위의 구현은 자기 할당을 관대하지만, *this
및 other
자기 이동 할당 후 크기가 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
.