함수 - 복사 및 스왑 관용구가 C++ 11의 복사 및 이동 관용구가되어야합니까?
복사생성자 사용이유 (2)
이 대답 에서 설명한 것처럼 복사 및 스왑 이디엄은 다음과 같이 구현됩니다.
class MyClass
{
private:
BigClass data;
UnmovableClass *dataPtr;
public:
MyClass()
: data(), dataPtr(new UnmovableClass) { }
MyClass(const MyClass& other)
: data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
MyClass(MyClass&& other)
: data(std::move(other.data)), dataPtr(other.dataPtr)
{ other.dataPtr= nullptr; }
~MyClass() { delete dataPtr; }
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, other.data);
swap(first.dataPtr, other.dataPtr);
}
MyClass& operator=(MyClass other)
{
swap(*this, other);
return *this;
}
};
operator =에 대한 매개 변수로 MyClass 값을 가짐으로써 매개 변수는 복사 생성자 또는 이동 생성자에 의해 생성 될 수 있습니다. 그런 다음 매개 변수에서 안전하게 데이터를 추출 할 수 있습니다. 이렇게하면 코드 중복을 방지하고 예외 안전을 지원합니다.
대답은 일시적으로 변수를 교환하거나 이동할 수 있음을 나타냅니다. 주로 스와핑에 대해 설명합니다. 그러나 컴파일러에 의해 최적화되지 않은 경우 스왑에는 세 가지 이동 작업이 포함되며 더 복잡한 경우 추가 추가 작업이 수행됩니다. 원하는 모든 것이 임시 개체를 할당 된 개체로 이동 하는 것입니다.
관찰자 패턴을 포함하는이보다 복잡한 예제를 고려하십시오. 이 예에서는 할당 연산자 코드를 직접 작성했습니다. 이동 생성자, 대입 연산자 및 스왑 메서드에 중점을 둡니다.
class MyClass : Observable::IObserver
{
private:
std::shared_ptr<Observable> observable;
public:
MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
friend void swap(MyClass& first, MyClass& second)
{
//Checks for nullptr and same observable omitted
using std::swap;
swap(first.observable, second.observable);
second.observable->unregisterObserver(first);
first.observable->registerObserver(first);
first.observable->unregisterObserver(second);
second.observable->registerObserver(second);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
}
분명히이 수동으로 작성된 대입 연산자의 코드 부분은 이동 생성자의 코드 부분과 동일합니다. 할당 연산자에서 스왑을 수행 할 수는 있지만 동작이 올바르지 만 잠재적으로 더 많은 이동을 수행하고 추가 등록 (스왑에서) 및 등록 해제 (소멸자에서)를 수행합니다.
대신에 생성자의 코드를 재사용하는 것이 더 합리적이지 않습니까?
private:
void performMoveActions(MyClass&& other)
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
public:
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
performMoveActions(other);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
performMoveActions(other);
}
이 접근 방식이 스왑 방식보다 열등하지 않은 것처럼 보입니다. 복사 - 스왑 (copy-and-swap) 관용구가 C ++ 11에서 복사 및 이동 관용구로서 더 나을 것이라고 생각하는 것이 맞습니까? 아니면 중요한 것을 놓쳤습니까?
각 특별 회원에게 가치있는 부드러운 사랑의 보살핌을주고 가능한 한 많이 기본 설정하십시오.
class MyClass
{
private:
BigClass data;
std::unique_ptr<UnmovableClass> dataPtr;
public:
MyClass() = default;
~MyClass() = default;
MyClass(const MyClass& other)
: data(other.data)
, dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
: nullptr)
{ }
MyClass& operator=(const MyClass& other)
{
if (this != &other)
{
data = other.data;
dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
: nullptr);
}
return *this;
}
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, second.data);
swap(first.dataPtr, second.dataPtr);
}
};
소멸자는 원하는 경우 위에 암시 적으로 기본값으로 설정할 수 있습니다. 이 예제에서는 다른 모든 것이 명시 적으로 정의되거나 기본값으로 설정되어야합니다.
참조 : http://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
복사 / 스왑 관용구가 성능에 영향을 줄 수 있습니다 (슬라이드 참조). 예를 들어, 왜 고성능 / std::vector
와 std::string
copy / swap을 사용하지 않는지 자주 사용하는지 궁금합니다. 성능 저하로 그 이유가 있습니다. BigClass
에 std::vector
또는 std::string
(가능성이있는 것)이 포함되어있는 경우 가장 좋은 방법은 특수 멤버에게 스페셜 멤버에게 전화하는 것입니다. 위의 방법을 수행하는 것입니다.
과제에서 강력한 예외 안전성이 필요한 경우 성능에 추가하여 제공하는 방법에 대한 슬라이드 ( "strong_assign"검색)를 참조하십시오.
우선, 클래스가 움직일 수있는 한 C ++ 11에서 swap
함수를 작성하는 것은 일반적으로 불필요합니다. 기본 swap
은 이동에 의존합니다.
void swap(T& left, T& right) {
T tmp(std::move(left));
left = std::move(right);
right = std::move(tmp);
}
그리고 그게 요소들입니다.
둘째로, 이것에 기반하여, Copy-And-Swap은 실제로 여전히 있습니다 :
T& T::operator=(T const& left) {
using std::swap;
T tmp(left);
swap(*this, tmp);
return *this;
}
// Let's not forget the move-assignment operator to power down the swap.
T& T::operator=(T&&) = default;
복사 및 스왑 (이동) 또는 이동 및 스왑 (이동)이며 항상 최적의 성능에 근접해야합니다. 몇 가지 여분의 과제가있을 수 있지만 잘하면 귀하의 컴파일러가 처리됩니다.
편집 : 이것은 복사 할당 연산자 만 구현합니다. 그렇지 않으면 스택 오버플로가 발생합니다 (move-assignment 및 swap이 서로를 무기한 호출 함).