"The committee shall make no rule that prevents C++ programmers from
"shooting themselves in the foot."
First Amendment to the C++ Standard quoted by Thomas Becker
http:
Therefore we should use std::move() with extra care.
-- Usually std::move() has positive effect on performance,
as many standard algorithm utilize move semantics.
-- Sometimes we have to use std::move().
There are types moveable but not copyable:
std::unique_pointer
std::fstream
std::thread
X&& f();
void g(X&& arg_)
{
X var1 = arg_;
X var2 = f();
}
Since arg_ is declared as rvalue reference we might think that here
X::X(X&& rhs) will be called. But not always:
Things that are declared as rvalue reference can be lvalues or rvalues.
Anything has a "name" is an "lvalue" otherwise it is an "rvalue".
Therefore in the var1 example above X::X(const X& will be called),
and in the var2 example X::X(&&) is called.
Philosophy:
After X var1 = arg_; arg_ is still in scope and can be available.
X&& f();
void g(X&& arg_)
{
X var1 = arg_;
X var2 = f();
}
void f()
{
X var1;
X var2 = std::move(var1);
var1.g();
}
#include <iostream>
struct MyString
{
MyString() { std::cerr << "defCtor" << std::endl; }
MyString(const MyString& rhs) { std::cerr << "copyCtor" << std::endl; }
MyString(MyString&& rhs) { std::cerr << "moveCtor" << std::endl; }
};
class MoveConstr
{
public:
MoveConstr( MyString s) : value(std::move(s)) { }
private:
MyString value;
};
int main()
{
MoveConstr mc(MyString());
return 0;
}
$ ./a.out
$
"Most vexing parse!!!"
MoveConstr mc(MyString())
--> Function mc returning MoveConstr has one parameter
a pointer to function returning MyString having no parameter
without parameter name.
MoveConstr mc(MyString()) === MoveConstr mc(MyString (*)())
$ clang++ -std=c++11 -Wvexing-parse m1.cpp
m1.cpp:21:16: warning: parentheses were disambiguated as a function declaration
[-Wvexing-parse]
MoveConstr mc(MyString());
^~~~~~~~~~~~
m1.cpp:21:17: note: add a pair of parentheses to declare a variable
MoveConstr mc(MyString());
^
( )
1 warning generated.
#include <iostream>
struct MyString
{
MyString() { std::cerr << "defCtor" << std::endl; }
MyString(const MyString& rhs) { std::cerr << "copyCtor" << std::endl; }
MyString(MyString&& rhs) { std::cerr << "moveCtor" << std::endl; }
};
class MoveConstr
{
public:
MoveConstr( MyString s) : value(std::move(s)) { }
private:
MyString value;
};
int main()
{
MoveConstr mc((MyString()));
return 0;
}
$ ./a.out
defCtor
moveCtor
$
#include <iostream>
struct MyString
{
MyString() { std::cerr << "defCtor" << std::endl; }
MyString(const MyString& rhs) { std::cerr << "copyCtor" << std::endl; }
MyString(MyString&& rhs) { std::cerr << "moveCtor" << std::endl; }
};
class MoveConstr
{
public:
MoveConstr( const MyString s) : value(std::move(s)) { }
private:
MyString value;
};
int main()
{
MoveConstr mc((MyString()));
return 0;
}
$ ./a.out
defCtor
copyCtor
$
#include <iostream>
struct MyString
{
MyString() { std::cerr << "defCtor" << std::endl; }
MyString(const MyString& rhs) { std::cerr << "copyCtor" << std::endl; }
MyString(MyString&& rhs) { std::cerr << "moveCtor" << std::endl; }
};
class MoveConstr
{
public:
MoveConstr( const MyString& s) : value(std::move(s)) { }
private:
MyString value;
};
int main()
{
MoveConstr mc((MyString()));
return 0;
}
$ ./a.out
defCtor
copyCtor
$
If-it-has-a-name-rule:
class Base
{
public:
Base(const Base& rhs);
Base(Base&& rhs);
:
};
class Derived : public Base
{
Derived(const Derived& rhs);
Derived(Derived&& rhs);
:
};
Derived(Derived const & rhs) : Base(rhs)
{
}
Derived(Derived&& rhs) : Base(rhs)
{
}
Derived(Derived&& rhs) : Base(std::move(rhs))
{
}
Performance
#include <utility>
class X
{
public:
X(int sz_);
X(const X& rhs_);
X(X&& rhs_);
~X();
private:
int _size;
int *_v;
};
X::X(int sz_) : _size(sz_), _v(new int[_size])
{
}
X::X(const X& rhs_) : _size(rhs_._size), _v(new int[_size])
{
for(int i = 0; i < _size; ++i)
_v[i] = rhs_._v[i];
}
X::X(X&& rhs_) : _size(rhs_._size)
{
_v = rhs_._v;
rhs_._size = 0;
rhs_._v = 0;
}
X::~X()
{
delete [] _v;
}
int main()
{
for( int i = 0; i < 1000; ++i)
{
X x(1000000);
X y(x);
}
return 0;
}
$ time ./a.out
real 0m18.324s
user 0m8.201s
sys 0m5.728s
int main()
{
for( int i = 0; i < 1000; ++i)
{
X x(1000000);
X y(std::move(x));
}
return 0;
}
$ time ./a.out
real 0m4.667s
user 0m0.700s
sys 0m2.980s
int main()
{
X x(1000000);
for( int i = 0; i < 1000; ++i)
{
X y(x);
}
return 0;
}
$ time ./a.out
real 0m11.662s
user 0m7.528s
sys 0m3.188s
int main()
{
X x(1000000);
for( int i = 0; i < 1000; ++i)
{
X y(std::move(x));
}
return 0;
}
X::X(const X& rhs_) : _size(rhs_._size), _v(new int[_size])
{
for(int i = 0; i < _size; ++i)
for (int j = 0; j < 1000; ++j )
_v[i] = rhs_._v[i];
}
int main()
{
X x(1000000);
X y(x);
return 0;
}
$ g++ -std=c++0x -pedantic -Wall r.cpp
time ./a.out
real 0m6.545s
user 0m6.124s
sys 0m0.028s
$ g++ -O3 -std=c++0x -pedantic -Wall r.cpp
time ./a.out
real 0m2.024s
user 0m1.840s
sys 0m0.024s
int main()
{
X x(1000000);
X y(std::move(x));
return 0;
}
$ g++ -std=c++0x -pedantic -Wall r.cpp
time ./a.out
real 0m0.015s
user 0m0.004s
sys 0m0.004s
$ g++ -O3 -std=c++0x -pedantic -Wall r.cpp
time ./a.out
real 0m0.015s
user 0m0.000s
sys 0m0.012s
X f()
{
X x(10000000);
return x;
}
int main()
{
X y(f());
return 0;
}
$ time ./a.out
real 0m0.043s
user 0m0.020s
sys 0m0.016s
X f()
{
X x(10000000);
return std::move(x);
}
int main()
{
X y(f());
return 0;
}
$ time ./a.out
real 0m0.039s
user 0m0.016s
sys 0m0.016s
There is almost no difference since modern compilers apply RVO
(return value optimization). That means, local x inside f() is
created in place of y.
In some cases, the performance even worst when using std::move()!
David Abrahams has an article on this:
http: