Move Semantics == Custom Swap Function Obsolete

Move semantics == custom swap function obsolete?

It is a matter of judgment. I will typically let std::swap do the job for prototyping code, but for release code write a custom swap. I can usually write a custom swap that is about twice as fast as 1 move construction + 2 move assignments + 1 resourceless destruction. However one may want to wait until std::swap actually proves to be a performance problem before going to the bother.

Update for Alf P. Steinbach:

20.2.2 [utility.swap] specifies that std::swap(T&, T&) has a noexcept equivalent to:

template <class T>
void
swap(T& a, T& b) noexcept
(
is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value
);

I.e. if move operations on T are noexcept, then std::swap on T is noexcept.

Note that this spec doesn't require move members. It only requires that construction and assignment from rvalues exists, and if it is noexcept, then swap will be noexcept. E.g.:

class A
{
public:
A(const A&) noexcept;
A& operator=(const A&) noexcept;
};

std::swap<A> is noexcept, even without move members.

Is specializing std::swap deprecated now that we have move semantics?

The specialization of std::swap is now optional, but not deprecated. The rationale is performance.

For prototyping code, and perhaps even for much shipping code, std::swap will be plenty fast. However if you're in a situation where you need to eek out every little bit from your code, writing a custom swap can still be a significant performance advantage.

Consider the case where your class essentially has one owning pointer and your move constructor and move assignment just have to deal with that one pointer. Count machine loads and stores for each member:

Move constructor: 1 load and 2 stores.

Move assignment: 2 loads and 2 stores.

Custom swap: 2 loads and 2 stores.

std::swap is 1 move construction and 2 move assignments, or: 5 loads and 6 stores.

A custom swap is potentially still two or three times faster than std::swap. Though any time you're trying to figure out the speed of something by counting loads and stores, both are going to be wicked fast.

Note: In computing the cost of your move assignment, be sure and take into account that you will be moving into a moved-from value (in the std::swap algorithm). This often negates the cost of a deallocation, though at the cost of a branch.

When to provide custom swap function in C++?

You should follow the rule of 0.

Non-resource managing classes should have default move/copy/assign/destruction.

Resource managing classes should be only concerned with managing resources.

Your code does not manage resources. Everything copy/move wise should be defaulted.

swap is a special case of move. In the case you are a resource managing type, you may want to also write it. However, like most things, it is an optimization, and you shoud only do such optimizations if you have proven it has performance impact.

An example of when it might have performance impact is if you have a never-empty resource-owning type and that resource isn't free, so creating a temporary object has unavoidable costs. std::swap by default moves through a throw-away object. A custom swap could avoid this.

Or, a type that is a large buffer of cheap to move elements; creating that large buffer could have real costs.

Otherwise, don't do it. And test if you do it that it helps. Custom swap is a optimization of move for a particular case.

Rationale behind the usual implemention of std::swap overloads

since S members are built-in types, is it fine (well-formed) to define it as below instead: [std::swap usage]

Yes. I mean, it works because std::swap just "swaps the values a and b" if they are both MoveAssignable and MoveConstructible, which built-in types are.

If the members are all built-in types (or pointer types, possibly pointers to custom types), is it still necessary/recommended to use that idiom instead of explicitly calling std::swap() ?

Just for the future of your class: if one day any of its members gets replaced with a user-defined type, you might forget to modify your swap and miss a "specialized" ADL implementation for it, still calling the generic std::swap which could (theoretically) be inefficient yet still compile.

Actually, you don't even need to think about it if you just always use boost::swap or implement it yourself:

template<typename T> void swap(T& a, T& b) {
using std::swap;
swap(a, b);
}

Using move semantics to avoid copying when pushing_back to a custom container does not avoid copying

When you call this line

myContainer.push_back(foo(a));

L-value is passed into push_back method, and now read about using std::forward - http://www.cplusplus.com/reference/utility/forward/,

Returns an rvalue reference to arg if arg is not an lvalue reference.

If arg is an lvalue reference, the function returns arg without modifying its type.

and in your push_back you call

new(data + size) T(std::forward<U>(value));

but value was passed as L-value, and only constructor MyClass(const MyClass &passedEntity) can be invoked.

If you want a object to be moved, you can write

myContainer.push_back(std::move(a)); // cast to R-reference

EDIT

You should not use move in your push_back function, below is simple example.
Suppose you have class like this:

 struct Foo {
int i;
Foo (int i = 0) : i(i) {
}
~Foo () {
}
Foo (const Foo& ) {
}
Foo& operator=(const Foo&) {
return *this;
}
Foo (Foo&& f)
{
i = f.i;
f.i = 0; // this is important
}
Foo& operator=(Foo&& f)
{
i = f.i;
f.i = 0; // this is important
return *this;
}
};

we have also 2 functions

template<class T> 
void process1 (const T& ) {
cout << "process1" << endl;
}

template<class T>
void process (T&& obj) {
cout << "process2" << endl;
T newObj = forward<T>(obj);
}

and bars functions are counterparts of your push_back method.

template <typename T>
void bar1 (T&& value) {
process (move(value)); // you use move in your push_back method
}

template <typename T>
void bar2 (T&& value) {
process (forward<T>(value));
}

now we must consider 4 cases:

[1] pass L-value, version with forward

Foo f(20);
bar2 (f);
cout << (f.i) << endl; // 20

[2] pass R-value, version with forward

Foo f(20);
bar2 (move(f));
cout << (f.i) << endl; // 0, it is OK bacuse we wanted to move 'f' object

[3] pass R-value, version with move

Foo f(20);
bar1 (move(f));
cout << (f.i) << endl; // 0, ok, we wanted to move 'f' object

[4] pass L-value, version with move in your push_back method

Foo f(20);
bar1 (f);
cout << (f.i) << endl; // 0 !!! is it OK ? WRONG

in last case, we passed f as L-value, but this object was moved in bar1 function, for me this is strange behavior and is incorrect.



Related Topics



Leave a reply



Submit