해야 복사-and-Swap 관용구가 사본 그리고 이동의 관용구에서는 C++11?
-
21-12-2019 - |
문제
로에서 설명 이 답변, copy-and-swap 관용은 다음과 같이 구현됩니다.
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;
}
};
의 값을 갖는의 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);
}
저 처럼 이 방법은 적 열등 스왑 접근 방식이다.나는 생각에 맞는 복사-and-swap 관용이 더 나을 것으로의 복사와 이동의 관용구에서는 C++11,또는 놓치지 않았다 중요한 것일까요?
해결책 3
이 질문을했기 때문에 오랜 시간이 지났습니다. 그리고 잠시 동안 대답을 알았지 만, 나는 그것에 대한 답을 작성하는 것을 막습니다. 여기
입니다대답은 아니오입니다. 복사 및 스왑 관용구는 복사 및 이동 관용구가되어서는 안됩니다.
복사 및 스왑의 중요한 부분 (또한 이동 구조 및 스왑이기도 함)은 안전한 정리가있는 할당 연산자를 구현하는 방법입니다. 이전 데이터는 복사본 구성 또는 이동 구성 임시로 스왑됩니다. 작업이 완료되면 임시가 삭제되고 소멸자가 호출됩니다.
스왑 동작은 파괴자를 재사용 할 수 있으므로 지정 연산자에 정리 코드를 작성할 필요가 없습니다.
완료 및 할당 할 정리 동작이 없으면 할당 연산자를 기본값으로 선언하고 복사 및 스왑이 필요하지 않아야합니다.
이동 생성자 자체는 일반적으로 새 객체이기 때문에 정리 동작이 필요하지 않습니다. 일반적인 간단한 접근 방식은 이동 생성자가 기본 생성자를 호출 한 다음 모든 구성원을 Move-from 객체로 스왑하는 것입니다. Moved-From 객체는 Bland 기본 구성 객체와 같습니다.
그러나이 질문의 관찰자 패턴 예제에서는 이전 오브젝트에 대한 참조가 변경 될 필요가 있으므로 실제로 여분의 정리 작업을 수행 해야하는 예외입니다. 일반적으로 관찰자와 관찰자 및 가능한 한 언제든지 참조가 불가능한 참조를 기반으로하는 다른 디자인 구조물을 만드는 것이 좋습니다.
다른 팁
각 특별한 회원에게 부드러운 사랑의 진료를받을 자격이 있으므로 가능한 한 기본값을 사용할 수 있습니다.
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 :: 유형과 같은 고성능 / 종종 사용되는 이유와 std::vector
는 사본 / 스왑을 사용하지 않는지 궁금합니다. 가난한 성능이 그 이유입니다. std::string
가 BigClass
또는 std::vector
(가능성이있는 것처럼 보이는), 가장 좋은 방법은 특별한 구성원에서 특별한 회원을 호출하는 것입니다. 위의 사항은 어떻게하는지입니다.
할당에 대한 강력한 예외 안전이 필요하면 성능 외에도 제공하는 방법에 대한 슬라이드 ( "strong_assign")를 참조하십시오.
첫째로,그것은 일반적으로필을 작성 swap
기능은 C++11 만큼의 클래스가 움직일 수 있습니다.기본 swap
이트를 이동:
void swap(T& left, T& right) {
T tmp(std::move(left));
left = std::move(right);
right = std::move(tmp);
}
그리고 그것의 요소를 교환합니다.
둘째,이를 바탕으로,복-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;
나 사본을 스왑(는 이동)또는 이동 스왑(는 이동),그리고 항상 달성에 가까운 최상의 작동 성능을 발휘합니다.이 있는 부부 중복 할당하지만,잘하면 컴파일러를 돌볼 것입니다.
편집: 이를 구현하는 복사 할당 운영자;별도의 이동 할당 운영자는 또한 필요할 수 있지만 기본적으로,그렇지 않으면 스택 오버플로우가 발생(이동 할당 및 스왑 통화 각 기타 무기한).