- CPU Caches and Why You Care. This is an excerpt from my training course, Fastware for C++.
- Move Semantics, Rvalue References, and Perfect Forwarding. This is an abbreviated version of my treatment of the topic in An Overview of the New C++, and the fact that the abbreviated version runs an hour and a half gives you some idea of the depth of the subject. (The full version, including time for Q&A about the kinds of obscure edge cases that programmers live for, goes three hours or longer, but given the intricacies of rvalue references and their pervasive impact on C++0x, I think the investment is worth it.)
Enjoy!
Scott
Much appreciated!
ReplyDeleteYes, thanks Scott, I very much enjoyed the talk. You mentioned that you might make the slides available (at least to those who were in attendance). I was not in attendance at the conference, unfortunately.
ReplyDeleteAre you willing to make the slide with the links you mentioned at the end available? I would like to follow up by reading that content. If you can, I would greatly appreciate it. Thanks!
The presentation materials are now available via this blog post as well as my Past Talks page.
ReplyDeleteScott
Thanks Scott, I appreciate it!
ReplyDeleteHello Scott,
ReplyDeletethank you very much for providing the videos and slides. It was very enlightening for me (especially the false sharing).
I have a question regarding the move assignment implementation.
In your presentation you have said that the Widget class implementation you presented is a conventional and widely accepted/expected one.
Stephan T. Lavavej's in his rvalue reference lecture presented a different version - the assignment with the rhs argument passed by value:
class Widget: public WidgetBase {
public:
Widget(Widget const& rhs) { … }
Widget(Widget&& rhs) { … }
Widget& operator=(Widget rhs)
{
rhs.swap(*this);
return *this;
}
…
};
I like this one very much because it is basically 2in1 implementation (it reminds me of the copy&swap idiom in the copy assignment).
What do you think about this version?
Thanks very much again.
Best Regards,
Petr
@filodej: The code you posted *is* the copy-and-swap idiom in the copy assignment operator. If copy-and-swap is how you want to implement copy assignment, what you show is a fine way to implement it. But for some types, copy-and-swap is more expensive than need be. An example of this would be the copy assignment operator for std::array. For a std::array object of size n, implementing the copy operator= via copy-and-swap means copying 3n values, but implementing it via the conventional approach (i.e., declaring operator='s parameter to be a reference) calls for copying only n values.
ReplyDeleteScott
Yes, you are right.
ReplyDeleteThe std::array<> seems to be a typical example where neither swap nor move can be implemented efficiently.
If we focus only on classes where the move semantics can buy us something then the single-assignment pattern seems better to me than the one with two assignments.
Although it seems to apply the copy&swap, the presence of the move constructor makes it actually much better - makes it (copy|move)&swap (compiler can decide whether it is possible to call move constructor instead of copy constructor for the rhs argument).
For example:
// move constructor + swap
Widget w = create_widget();
// copy constructor + swap
Widget w2 = w;
Or did I miss something?
Kind regards,
Petr
As a general rule, you don't want to implement the move operations using swap, i.e., move-and-swap is generally an anti-pattern. This is what I say about it in my C++11 training materials:
ReplyDeleteA generic, “clever” (i.e., suspicious) way to implement move assignment for a type T is
T& operator=(T&& rhs) {
swap(rhs, *this);
return *this;
}
This has the effect of swapping the contents of *this and rhs. The idea is that because rhs is an rvalue reference, it’s bound to an rvalue, and that rvalue will be destroyed at the end of the statement containing the assignment. When it is, the data formerly associated with *this will be destroyed (e.g., resources will be released). The problem is that rhs may actually correspond to an lvalue that has been explicitly std::move’d, and in that case, the lvalue may not be destroyed until later than expected. That can be problematic. Details can be found at http://thbecker.net/articles/rvalue_references/section_04.html and http://cpp-next.com/archive/2009/09/your-next-assignment/.
Hi Scott,
ReplyDeletethank you for the links (especially the latter one gave me exactly the information I was looking for).
Just to summarize my understanding about implementing the move assignment operator:
- Using swap with argument passed as rvalue reference is not a good idea (due to possibly postponed user-visible side-effects of the left-hand-side)
- Using swap with argument passed as value is OK, but may be slightly less efficient when assigning from an lvalue (due to copying "too eagerly")
Thanks very much for your time.
Petr
If you're implementing a move operation (i.e., move constructor or move assignment operator), you will, by definition, always want to take your parameter by rvalue reference. If you take your parameter by value, it's not a move operation.
ReplyDeleteRegarding the choice between passing by value or passing by lvalue-reference-to-const, my advice is to pass by value only if you will unconditionally make a copy of the parameter. In that case, passing by value permits compilers to perform some optimizations they might otherwise not perform. But if the copy is not unconditional, you can end up paying for a copy where none is required. (Note that his advice is not specific to the copy assignment operator. It applies to all functions. The only thing special about the copy assignment operator is that it often performs an unconditional copy of its parameter.)
Scott
Sorry for reiterating the issue again, but still I am little bit confused. It seems to me that if I implement move constructor and then the assignment operator accepting parameter by value, like this:
ReplyDeleteclass Widget: public WidgetBase {
public:
Widget(Widget const& rhs): data(rhs.data) {}
Widget(Widget&& rhs): data(std::move(rhs.data) {}
Widget& operator=(Widget rhs)
{
rhs.swap(*this);
return *this;
}
…
private:
std::vector data;
};
then in effect I have implemented the move assignment (because it takes advantage of the move constructor for rhs value creation whenever possible) - e.g. in the following statement:
Widget w = create_widget();
the following sequence occurs:
1) A new Widget is created and returned as rvalue
2) A new temporary is created via move constructor
3) w.operator= swaps *this and the rhs temporary
The article Your new assignment you linked in your previous answer considers this approach as a "canonical" form of implementing copy+move assignments both at once.
Best regards,
Petr
The cost of a move assignment in your approach consists of the following:
ReplyDelete1. Move-construct rhs.
2. Move rhs's value into *this, which in your implementation is accomplished via a swap.
3. Destruct rhs.
In a "real" move assignment operator, there would be no need for steps 1 and 3.
Scott