Is the copy and swap idiom still useful in C++11 - c++

I refer to this question:
What is the copy-and-swap idiom?
Effectively, the above answer leads to the following implementation:
class MyClass
{
public:
friend void swap(MyClass & lhs, MyClass & rhs) noexcept;
MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};
void swap( MyClass & lhs, MyClass & rhs )
{
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
}
However, notice that we could eschew the swap() altogether, doing the following:
class MyClass
{
public:
MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs); }
MyClass & operator=(MyClass rhs)
{
/* put swap code here */
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
// :::
return *this;
}
};
Note that this means that we will no longer have a valid argument dependent lookup on std::swap with MyClass.
In short is there any advantage of having the swap() method.
edit:
I realized there is a terrible mistake in the second implementation above, and its quite a big thing so I will leave it as-is to instruct anybody who comes across this.
if operator = is defined as
MyClass2 & operator=(MyClass2 rhs)
Then whenever rhs is a r-value, the move constructor will be called. However, this means that when using:
MyClass2(MyClass2 && rhs)
{
//*this = std::move(rhs);
}
Notice you end up with a recursive call to the move constructor, as operator= calls the move constructor...
This is very subtle and hard to spot until you get a runtime stack overflow.
Now the fix to that would be to have both
MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)
this allows us to define the copy constructors as
MyClass2(const MyClass2 & rhs)
{
operator=( rhs );
}
MyClass2(MyClass2 && rhs)
{
operator=( std::move(rhs) );
}
Notice that you write the same amount of code, the copy constructors come "for-free" and you just write operator=(&) instead of the copy constructor and operator=(&&) instead of the swap() method.

First of all, you're doing it wrong anyway. The copy-and-swap idiom is there to reuse the constructor for the assignment operator (and not the other way around), profiting from already properly constructing constructor code and guaranteeing strong exception safety for the assignment operator. But you don't call swap in the move constructor. In the same way the copy constructor copies all data (whatever that means in the given context of an individual class), the move constructor moves this data, your move constructor constructs and assigns/swaps:
MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
And this would in your alternative version just be
MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }
Which doesn't exhibit the severe error introduced by calling the assignment operator inside the constructor. You should never ever call the assignment operator or swap the whole object inside a constructor. Constructors are there to care for construction and have the advantage of not having to care for the, well, destruction of the previous data, since that data doesn't exist yet. And likewise can constructors handle types not default constructible and last but not least often direct construction can be more performant than defualt construction followed by assignment/swap.
But to answer your question, this whole thing is still the copy-and-swap idiom, just without an explicit swap function. And in C++11 it is even more useful because now you have implemented both copy and move assignment with a single function.
If the swap function is still of value outside of the assignment operator is an entirely different question and depends if this type is likely to be swapped, anyway. In fact in C++11 types with proper move semantics can just be swapped sufficiently efficient using the default std::swap implementation, often eliminating the need for an additional custom swap. Just be sure not to call this default std::swap inside of your assignment operator, since it does a move assignment itself (which would lead to the same problems as your wrong implementation of the move constructor).
But to say it again, custom swap function or not, this doesn't change anything in the usefulness of the copy-and-swap idiom, which is even more useful in C++11, eliminating the need to implement an additional function.

You're certainly not considering the whole picture. Your code re-uses constructors for different assignment operators, the original re-uses assignment operators for different constructors. This is basically the same thing, all you've done is shift it around.
Except that since they write constructors, they can deal with non-default-constructible types or types whose values are bad if not initialized explicitly like int or are plain expensive to default-construct or where the default-constructed members are not valid to destruct (for example, consider a smart pointer- an uninitialized T* leads to a bad delete).
So basically, all you've achieved is the same principle but in a decidedly worse place. Oh, and you had to define all four functions, else mutual recursion, whereas the original copy-and-swap only defined three functions.

The validity of the reasons (if any) to use the copy-and-swap idiom to implement copy assignment are the same in C++11 as they are in previous versions.
Also note that you should use std::move on member variables in the move constructor, and you should use std::move on any rvalue references that are function parameters.
std::forward should only be used for template parameter references of the form T&& and also auto&& variables (which both may be subject to reference folding into lvalue references during type deduction) to preserve their rvalueness or lvalueness as appropriate.

Related

Reduce Copy Constructor Calls

The following code is a minimal example from a project that I'm working on. The main question is that I want to cut down the number of calls to the copy constructor, but it is not clear to me the right way to do this.
#include<iostream>
class MyClass
{
public:
MyClass() {std::cout << "Default Constructor\n";}
MyClass(const MyClass &input) {std::cout << "Copy Constructor\n";}
MyClass & operator=(const MyClass &input)
{std::cout << "Assignment\n"; return *this;}
MyClass & operator+=(const MyClass &input) {return *this;}
friend MyClass operator+(MyClass lhs,const MyClass &);
};
MyClass operator+(MyClass lhs,const MyClass &rhs)
{lhs+=rhs;return lhs;}
int main()
{
MyClass a,b,c;
c=a+b;
return 0;
}
When I run the code, the output is:
Default Constructor
Default Constructor
Default Constructor
Copy Constructor
Copy Constructor
Assignment
The three default constructors are called in the construction of a, b, and c.
The two copy constructors are called for the first argument in operator+ and the return for operator+.
The assignment assigns the result from assigning a+b to c.
Main Question: In my application, the copy constructor is expensive (it involves memory allocation). Assignment, on the other hand, is relatively cheap. What is the proper way to have fewer calls to the copy constructor?
I've considered a few solutions, but none make me happy:
As I understand, from reading, the operator+ should not have a reference for the first argument since this helps with chaining temporaries. Therefore, this copy constructor seems unavoidable.
The following code is significantly faster (due to no copy constructor calls): c = a; c += b; I could write my code using this format, but this requires more a more delicate approach. I would prefer the compiler to be smarter than for me to make these tweaks myself.
I could implement a function add(MyClass &,const MyClass &,const MyClass &); but this loses the ease of using the addition operator (and requires a lot of (mindless) coding due to the number of different data types that I'm using).
I've looked at the questions, but I don't see any suggestions that might improve performance in this case:
Copy constructor called twice, Copy constructor called twice, and Conditions for copy elision
Responses to comments:
The private data includes MPFR's and MPFI's and the constructor includes initialization of this data. Perhaps a different implementation of the constructors would be appropriate, but I'm not sure.
I considered a move constructor, but there are times when I want a copy copy assignment as well. From cppreference it appears that these cannot coexist (or at least there was an error when I tried it at first). It appears that this should be the best option.
In other to minimize copy constructor calls, I recommend you define the move constructor and that you perfect-forward your operator arguments. Move constructor:
MyClass(MyClass &&input) {std::cout << "Move Constructor\n";}
Perfect-forwarding operator:
template<typename T>
friend MyClass operator+(T &&lhs,T &&rhs) {return std::forward<T>(lhs);}
With the right calls, your operator will involve a move constructor instead of a copy constructor. For instance, if you add objects that come out of a function and store the result immediately (e.g. MyClass c=a+b; instead of MyClass c;c=a+b;), thanks to RVO you can save the copy constructor.
Let's say you have a function that returns a MyClass instance:
MyClass something() {return MyClass();}
If you add the function return values and store them immediately, e.g.:
MyClass c=something()+something();
Then no copy constructor will be ever involved.
I have put a series of examples here where I used the const MyClass& parameter with operator+ and perfect-forwarding parameters with operator-. You can see that it makes a difference in the last example but not in all the other ones. That's why I said "with the right calls". If you have to manipulate objects that can be forwarded like that, it might be worth a shot.
You're passing lhs by copy. This is why you have additional copy constructor call. Modify your operator+:
MyClass operator+(const MyClass &lhs, const MyClass &rhs)

Copy & Move Idiom?

By using the Copy & Swap idiom we can easily implement copy assignment with strong exception safety:
T& operator = (T other){
using std::swap;
swap(*this, other);
return *this;
}
However this requires T to be Swappable. Which a type automatically is if std::is_move_constructible_v<T> && std::is_move_assignable_v<T> == true thanks to std::swap.
My question is, is there any downside to using a "Copy & Move" idiom instead? Like so:
T& operator = (T other){
*this = std::move(other);
return *this;
}
provided that you implement move-assignment for T because obviously you end up with infinite recursion otherwise.
This question is different from Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11? in that this question is more general and uses the move assignment operator instead of actually moving the members manually. Which avoids the issues with clean-up that predicted the answer in the linked thread.
Correction to the question
The way to implement Copy & Move has to be as #Raxvan pointed out:
T& operator=(const T& other){
*this = T(other);
return *this;
}
but without the std::move as T(other) already is an rvalue and clang will emit a warning about pessimisation when using std::move here.
Summary
When a move assignment operator exists, the difference between Copy & Swap and Copy & Move is dependent on whether the user is using a swap method which has better exception safety than the move assignment. For the standard std::swap the exception safety is identical between Copy & Swap and Copy & Move. I believe that most of the time, it will be the case that swap and the move assignment will have the same exception safety (but not always).
Implementing Copy & Move has a risk where if the move assignment operator isn't present or has the wrong signature, the copy assignment operator will reduce to infinite recursion. However at least clang warns about this and by passing -Werror=infinite-recursion to the compiler this fear can be removed, which quite frankly is beyond me why that is not an error by default, but I digress.
Motivation
I have done some testing and a lot of head scratching and here is what I have found out:
If you have a move assignment operator, the "proper" way of doing Copy & Swap won't work due to the call to operator=(T) being ambiguous with operator=(T&&). As #Raxvan pointed out, you need to do the copy construction inside of the body of the copy assignment operator. This is considered inferior as it prevents the compiler from performing copy elision when the operator is called with an rvalue. However the cases where copy elision would have applied are handled by the move assignment now so that point is moot.
We have to compare:
T& operator=(const T& other){
using std::swap;
swap(*this, T(other));
return *this;
}
to:
T& operator=(const T& other){
*this = T(other);
return *this;
}
If the user isn't using a custom swap, then the templated std::swap(a,b) is used. Which essentially does this:
template<typename T>
void swap(T& a, T& b){
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
Which means that the exception safety of Copy & Swap is the same exception safety as the weaker of move construction and move assignment. If the user is using a custom swap, then of course the exception safety is dictated by that swap function.
In the Copy & Move, the exception safety is dictated entirely by the move assignment operator.
I believe that looking at performance here is kind of moot as compiler optimizations will likely make there be no difference in most cases. But I'll remark on it anyway the copy and swap performs a copy construction, a move construction and two move assignments, compared to Copy & Move which does a copy construction and only one move assignment. Although I'm kind of expecting the compiler to crank out the same machine code in most cases, of course depending on T.
Addendum: The code I used
class T {
public:
T() = default;
T(const std::string& n) : name(n) {}
T(const T& other) = default;
#if 0
// Normal Copy & Swap.
//
// Requires this to be Swappable and copy constructible.
//
// Strong exception safety if `std::is_nothrow_swappable_v<T> == true` or user provided
// swap has strong exception safety. Note that if `std::is_nothrow_move_assignable` and
// `std::is_nothrow_move_constructible` are both true, then `std::is_nothrow_swappable`
// is also true but it does not hold that if either of the above are true that T is not
// nothrow swappable as the user may have provided a specialized swap.
//
// Doesn't work in presence of a move assignment operator as T t1 = std::move(t2) becomes
// ambiguous.
T& operator=(T other) {
using std::swap;
swap(*this, other);
return *this;
}
#endif
#if 0
// Copy & Swap in presence of copy-assignment.
//
// Requries this to be Swappable and copy constructible.
//
// Same exception safety as the normal Copy & Swap.
//
// Usually considered inferor to normal Copy & Swap as the compiler now cannot perform
// copy elision when called with an rvalue. However in the presence of a move assignment
// this is moot as any rvalue will bind to the move-assignment instead.
T& operator=(const T& other) {
using std::swap;
swap(*this, T(other));
return *this;
}
#endif
#if 1
// Copy & Move
//
// Requires move-assignment to be implemented and this to be copy constructible.
//
// Exception safety, same as move assignment operator.
//
// If move assignment is not implemented, the assignment to this in the body
// will bind to this function and an infinite recursion will follow.
T& operator=(const T& other) {
// Clang emits the following if a user or default defined move operator is not present.
// > "warning: all paths through this function will call itself [-Winfinite-recursion]"
// I recommend "-Werror=infinite-recursion" or "-Werror" compiler flags to turn this into an
// error.
// This assert will not protect against missing move-assignment operator.
static_assert(std::is_move_assignable<T>::value, "Must be move assignable!");
// Note that the following will cause clang to emit:
// warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
// *this = std::move(T{other});
// The move doesn't do anything anyway so write it like this;
*this = T(other);
return *this;
}
#endif
#if 1
T& operator=(T&& other) {
// This will cause infinite loop if user defined swap is not defined or findable by ADL
// as the templated std::swap will use move assignment.
// using std::swap;
// swap(*this, other);
name = std::move(other.name);
return *this;
}
#endif
private:
std::string name;
};
My question is, is there any downside to using a "Copy & Move" idiom instead?
Yes, it you get stack overflow if you din't implement move assignmentoperator =(T&&).
If you do want to implement that you get a compiler error (example here):
struct test
{
test() = default;
test(const test &) = default;
test & operator = (test t)
{
(*this) = std::move(t);
return (*this);
}
test & operator = (test &&)
{
return (*this);
}
};
and if you do test a,b; a = b; you get the error:
error: ambiguous overload for 'operator=' (operand types are 'test' and 'std::remove_reference<test&>::type {aka test}')
One way to solve this is to use a copy constructor:
test & operator = (const test& t)
{
*this = std::move(test(t));
return *this;
}
This will work, however if you don't implement move assignment you might not get an error (depending on compiler settings). Considering human error, it's possible that this case could happen and you end up stack overflow at runtime which is bad.

How does c++11 implements "... = default;" for the rule of three methods

When I learned C++ people told me to always implement at least rule of three methods.
Now I'm seeing the new "... = default;" from c++0x on stack overflow, and my question is:
Is there a c++11 standard implementation defined for those methods or is it compiler specific?
plus I would like to have some precisions:
What does the implementation looks like in term of code? (if it's generic)
Does this have an advantage compared to my example implementation below?
If you don't use assignment/copy constructor, what does *... = delete* do precisly, what's the difference with declaring them private? Answer (from #40two)
Is the new default= different from the old default implementation?
Disclaimer: when I'll need more advanced features in my methods, for sure I'll implements them myself. But I get used to implement assignment operator and copy constructor even when I never used them, just in order that the compiler don't.
What I used to do: (edited, #DDrmmr swap/move)
//File T.h
class T
{
public:
T(void);
T(const T &other);
T(const T &&other);
T &operator=(T other);
friend void swap(T &first, T &second);
~T(void);
protected:
int *_param;
};
//File T.cpp
T::T(void) :
_param(std::null)
{}
T::T(T &other)
: _param(other._param)
{}
T::T(T &&other)
: T()
{
swap(*this, other);
}
T &T::operator=(T other)
{
swap(*this, other);
return (*this);
}
friend void swap(T &first, T &second)
{
using std::swap;
swap(first._param, second._param);
}
T::~T(void)
{}
The default behavior is:
Default ctor ( T() ): calls bases def. ctors and members default ctors.
Copy ctor ( T(const T&) ): calls bases copy. ctors and members copy ctors.
Move ctor ( T(T&&) ): calls bases move. ctors and members move ctors.
Assign ( T& operator=(const T&) ): calls bases assign. and members assign.
Transfer ( T& operator=(T&&) ): calls bases transfer, and members transfer.
Destructor ( ~T() ): calls member destructor, and bases destructor (reverse order).
For built-in types (int etc.)
Default ctor: set to 0 if explicitly called
Copy ctor: bitwise copy
Move ctor: bitwise copy (no change on the source)
Assign: bitwise copy
Transfer: bitwise copy
Destructor: does nothing.
Since pointers are builtin types as well, this apply to int* ( not to what it points to).
Now, if you don't declare anything, your T class will just hold a int* that does not own the pointed int, so a copy of T will just hold a pointer to the same int. This is the same resulting behavior as C++03. Default implemented move for built-in types are copy. For classes are memberwise move (and depends on what members are: just copies for built-ins)
If you have to change this behavior, you have to do it coherently: for example, if you want to "own" what you point to, you need
a default ctor initializing to nullptr: this defines an "empty state" we can refer later
a creator ctor initializing to a given pointer
a copy ctor initializing to a copy of the pointed (this is the real change)
a dtor that deletes the pointed
an assign that deletes the pointed and receive a new copy of the pointed
.
T::T() :_param() {}
T::T(int* s) :_param(s) {}
T(const T& s) :_param(s._param? new int(*s._param): nullptr) {}
~T() { delete _param; } // will do nothing if _param is nullptr
Let's not define the assign, by now, but concentrate on the move:
If you don't declare it, since you declared the copy, it will be deleted: this makes a T object always being copied even if temporary (same behavior as c++03)
But if the source object is temporary, we can create an empty destination and swap them:
T::T(T&& s) :T() { std::swap(_param, s._param); }
This is what is called a move.
Now the assignment: before C++11 T& operator=(const T& s) should check against a self assignment, make the destination empty and receive a copy of the pointed:
T& operator=(const T& s)
{
if(this == &s) return *this; // we can shortcut
int* p = new int(s._param); //get the copy ...
delete _param; //.. and if succeeded (no exception while copying) ...
_param = p; // ... delete the old and keep the copy
return *this;
}
With C++11 we can use the parameter passing to generate the copy, thus giving
T& operator=(T s) //note the signature
{ std::swap(_param, s._param); return *this; }
Note that this works also in C++98, but the pass-by copy will not be optimized in pass-by move if s is temporary. This makes such implementation not profitable in C++98 and C++03 but really convenient in C++11.
Note also that there is no need to specialize std::swap for T: std::swap(a,b); will work, being implemented as three moves (not copy)
The practice to implement a swap function derives for the case where T has many members, being swap required in both move and assign. But it can be a regular private member function.

Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11?

As explained in this answer, the copy-and-swap idiom is implemented as follows:
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;
}
};
By having a value of MyClass as parameter for operator=, the parameter can be constructed by either the copy constructor or the move constructor. You can then safely extract the data from the parameter. This prevents code duplication and assists in exception safety.
The answer mentions you can either swap or move the variables in the temporary. It primarily discusses swapping. However, a swap, if not optimised by the compiler, involves three move operations, and in more complex cases does additional extra work. When all you want, is to move the temporary into the assigned-to object.
Consider this more complex example, involving the observer pattern. In this example, I've written the assignment operator code manually. Emphasis is on the move constructor, assignment operator and swap method:
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);
}
}
Clearly, the duplicated part of the code in this manually written assignment operator is identical to that of the move constructor. You could perform a swap in the assignment operator and the behaviour would be right, but it would potentially perform more moves and perform an extra registration (in the swap) and unregistration (in the destructor).
Wouldn't it make much more sense to reuse the move constructor's code in stead?
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);
}
It looks to me like this approach is never inferior to the swap approach. Am I right in thinking that the copy-and-swap idiom would be better off as the copy-and-move idiom in C++11, or did I miss something important?
First of all, it is generally unnecessary to write a swap function in C++11 as long as your class is movable. The default swap will resort to moves:
void swap(T& left, T& right) {
T tmp(std::move(left));
left = std::move(right);
right = std::move(tmp);
}
And that's it, the elements are swapped.
Second, based on this, the Copy-And-Swap actually still holds:
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;
Will either copy and swap (which is a move) or move and swap (which is a move), and should always achieve close to the optimum performance. There might be a couple redundant assignments, but hopefully your compiler will take care of it.
EDIT: this only implements the copy-assignment operator; a separate move-assignment operator is also required, though it can be defaulted, otherwise a stack overflow will occur (move-assignment and swap calling each other indefinitely).
Give each special member the tender loving care it deserves, and try to default them as much as possible:
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);
}
};
The destructor could be implicitly defaulted above if desired. Everything else needs to be explicitly defined or defaulted for this example.
Reference: http://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
The copy/swap idiom will likely cost you performance (see the slides). For example ever wonder why high performance / often used std::types like std::vector and std::string don't use copy/swap? Poor performance is the reason. If BigClass contains any std::vectors or std::strings (which seems likely), your best bet is to call their special members from your special members. The above is how to do that.
If you need strong exception safety on the assignment, see the slides for how to offer that in addition to performance (search for "strong_assign").
It's been a long time since I asked this question, and I've known the answer for a while now, but I've put off writing the answer for it. Here it is.
The answer is no. The Copy-and-swap idiom should not become the Copy-and-move idiom.
An important part of Copy-and-swap (which is also Move-construct-and-swap) is a way to implement assignment operators with safe cleanup. The old data is swapped into a copy-constructed or move-constructed temporary. When the operation is done, the temporary is deleted, and its destructor is called.
The swap behaviour is there to be able to reuse the destructor, so you don't have to write any cleanup code in your assignment operators.
If there's no cleanup behaviour to be done and only assignment, then you should be able to declare the assignment operators as default and copy-and-swap isn't needed.
The move constructor itself usually doesn't require any clean-up behaviour, since it's a new object. The general simple approach is to make the move constructor invoke the default constructor, and then swap all the members with the move-from object. The moved-from object will then be like a bland default-constructed object.
However, in this question's observer pattern example, that's actually an exception where you have to do extra cleanup work because references to the old object need to be changed. In general, I would recommend making your observers and observables, and other design constructs based around references, unmovable whenever possible.

Implement copy-ctor in terms of copy-operator or separately?

This is not a duplicate of Implementing the copy constructor in terms of operator= but is a more specific question. (Or so I like to think.)
Intro
Given a (hypothetical) class like this:
struct FooBar {
long id;
double valX;
double valZ;
long valN;
bool flag;
NonCopyable implementation_detail; // cannot and must not be copied
// ...
};
we cannot copy this by the default generated functions, because you can neither copy construct nor copy a NonCopyable object. However, this part of the object is an implementation detail we are actually not interested in copying.
It does also does not make any sense to write a swap function for this, because the swap function could just replicate what std::swap does (minus the NonCopyable).
So if we want to copy these objects, we are left with implementing the copy-ctor and copy-operator ourselves. This is trivially done by just assigning the other members.
Question
If we need to implement copy ctor and operator, should we implement the copy ctor in terms of the copy operator, or should we "duplicate" the code with initialization list?
That is, given:
FooBar& operator=(FooBar const& rhs) {
// no self assignment check necessary
id = rhs.id;
valX = rhs.valX;
valZ = rhs.valZ;
valN = rhs.valN;
flag = rhs.flag;
// don't copy implementation_detail
return *this;
}
Should we write a)
FooBar(FooBar const& rhs) {
*this = rhs;
}
or b)
FooBar(FooBar const& rhs)
: id(rhs.id)
, valX(rhs.valX)
, valZ(rhs.valZ)
, valN(rhs.valN)
, flag(rhs.flag)
// don't copy implementation_detail
{ }
Possible aspects for an answer would be performance vs. maintainability vs. readability.
Normally you implement assignment operator in terms of copy constructor (#Roger Pate's version):
FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}
This requires providing a swap function which swaps relevant members (all except implementation_detail in your case).
If swap doesn't throw this approach guarantees that object is not left in some inconsistent state (with only part members assigned).
However in your case since neither copy constructor, nor assignment operator can throw implementing copy constructor in terms of assignment operator (a) is also fine and is more maintainable then having almost identical code in both places (b).
In general, I prefer b) over a) as it explicitly avoids any default construction of members. For ints, doubles etc. that isn't a consideration, but it can be for members with expensive operations or side effects. It's more maintainable if you don't have to consider this potential cost/issue as you're adding and removing members. Initialiser lists also support references and non-default-constructable elements.
Alternatively, you could have a sub-structure for the non-"implementation detail" members and let the compiler generate copying code, along the lines:
struct X
{
struct I
{
int x_;
int y_;
} i_;
int z_;
X() { }
X(const X& rhs)
: i_(rhs.i_), z_(0) // implementation not copied
{ }
X& operator=(const X& rhs)
{
i_ = rhs.i_;
return *this;
}
};
If you're really bothered about replicating std::swap, why not put everything other than the implementation detail into a struct?
struct FooBarCore {
long id;
double valX;
double valZ;
long valN;
bool flag;
// ...
};
struct FooBar {
FooBarCore core_;
NonCopyable implementation_detail; // cannot and must not be copied
};
then you can use std::swap for this struct in your copy function for FooBar.
FooBar& operator=(const FooBar &src) {
FooBarCore temp(src.core_)
swap(temp,*this.core_);
return *this;
}
Okay, another try, based on my comment to this answer.
Wrap the implementation_detail in a copyable class:
class ImplementationDetail
{
public:
ImplementationDetail() {}
ImplementationDetail(const ImplementationDetail&) {}
ImplementationDetail& operator=(const ImplementationDetail&) {}
public: // To make the example short
Uncopyable implementation_detail;
};
and use this class in your FooBar. The default generated Copy Constructor and Copy Assignment Operator for Foobar will work correctly.
Maybe it could even derive from Uncopyable so you don't get implementation_detail.implementation_detail all over your code. Or if you control the code to the implementation_detail class, just add the empty Copy Constructor and empty Assignment Operator.
If the Copy Constructor does not need to copy implementation_detail and still will be correct (I doubt the latter, but let's assume it for the moment), the implementation_detail is superfluous.
So the solution seems to be: make the implementation_detail static and rely on the default generated Copy Constructor and Assignment Operator.