C++11 rvalue reference calling copy constructor too
Put noexcept
on your move constructor:
TestClass(TestClass&& other) noexcept {
Elaboration: I was going to give this one Pierre, but unfortunately the cppreference source is only approximately correct.
In C++03
vector<T>::push_back(T)
has the "strong exception guarantee". That means that if the push_back
throws an exception, the vector is left in the same state it had prior to the call to push_back
.
This guarantee is problematic if the move constructor throws an exception.
When the vector
reallocates, it would like to move the elements from the old buffer to the new. However if any one of those moves throws an exception (besides the first), then it is left in a state where the old buffer has been modified, and the new buffer doesn't yet contain everything it is supposed to. The vector
can't restore the old buffer to its original state because it would have to move elements back to do so, those moves might also fail.
So a rule was laid down for C++11:
If
T
has anoexcept
move constructor, that can be used to move the elements from the old buffer to the new.Otherwise if
T
has a copy constructor, that will be used instead.Otherwise (if there is no accessible copy constructor), then the move constructor will be used after all, however in this case, the strong exception safety guarantee is no longer given.
Clarification: "copy constructor" in rule 2 means a constructor taking a const T&
, not one of those weenie so-called T&
copy constructors. :-)
Generic copy-constructor with rvalue-reference members
Here be dragons.
logger(logger const& other) : value_(other.value_)
The expression other.value_
is an lvalue of type T const
, e.g. int&
, int&&
or int const
.
If
T == int&&
, you need to do amove
, as the expression is an lvalue. Themove
is equivalent to astatic_cast<int&&>
, so you could do thestatic_cast
directly as well.If
T == int&
, no cast is required.If
T == int
, no cast is required.
For a copy ctor defined as:
logger(logger const& other) : value_(static_cast<T>(other.value_)) {/*...*/}
Applied to the third case, this is defined as the introduction of a temporary, and could result in an additional copy/move, although I think it can&will be elided.
A solution without relying on the copy/move elision is to introduce a weird_cast
, that yields the desired type in any case:
#include <type_traits>
template<class T, class U>
typename std::enable_if<std::is_reference<T>{}, T>::type
weird_cast(U& p)
{
return static_cast<T>(p);
}
template<class T, class U>
typename std::enable_if<not std::is_reference<T>{}, T const&>::type
weird_cast(U const& p)
{
return p;
}
int main()
{
int o = 42;
int & lo = o;
int && ro = std::move(o);
int const lco = o;
int&& r = weird_cast<int&&>(ro);
int& l = weird_cast<int& >(lo);
int d = weird_cast<int >(lco);
}
This is similar to std::forward
, but also supports "forwarding" non-reference types.
Where are the dragons?
[class.copy]/11 specifies:
A defaulted copy/move constructor for a class
X
is defined as deleted ifX
has:
- [...]
- for the copy constructor, a non-static data member of rvalue reference type
- [...]
An rvalue reference is typically bound to an xvalue or prvalue, i.e. to an expression referring to an object that is "near the end of its lifetime". As lifetime doesn't get extended through function boundaries, it would be error prone to allow such a "copying".
Overloading a function to use an rvalue reference instead of an lvalue reference
the code is the same for both since it's not a shallow copy, right?
The List(const List &rhs)
copy constructor is expected to deep copy (not shallow copy) the data (not the nodes themselves) from rhs
to this
, but otherwise leave rhs
intact and untouched.
The List(List &&rhs)
move constructor is expected to move (steal) the nodes themselves from rhs
to this
and leave rhs
in an empty state.
So, no, they would not be using the same code, not even close.
Same thing with the operator=
copy assignment and move assignment operators (which are commonly implemented utilizing the copy/move constructors via the copy-swap idiom to avoid code duplication).
For example:
template<typename T>
class List
{
private:
struct node
{
T data;
node *previous;
node *next;
};
node *head = nullptr;
node *tail = nullptr;
size_t size = 0;
public:
// default constructor
List() = default;
// copy constructor
List(const List &rhs)
{
node **newNode = &head;
for(node *curNode = rhs.head; curNode; curNode = curNode->next)
{
*newNode = new node{curNode->data, tail, nullptr};
tail = *newNode;
++size;
newNode = &(tail->next);
}
}
// move constructor
List(List &&rhs)
: head(rhs.head), tail(rhs.tail), size(rhs.size)
{
rhs.head = rhs.tail = nullptr;
rhs.size = 0;
}
// destructor
~List()
{
node *curNode = head;
while (curNode) {
node *next = curNode->next;
delete curNode;
curNode = next;
}
}
// copy assignment
List& operator=(const List &rhs)
{
if (this != &rhs) {
List tmp(rhs);
std::swap(head, tmp.head);
}
return *this;
}
// move assignment
List& operator=(List &&rhs)
{
List tmp(std::move(rhs));
std::swap(head, tmp.head);
return *this;
}
/*
Alternatively, you can use just 1 implementation of operator=
for both copy and move assignments, by taking the input parameter
*by value* and letting the compiler decide which constructor
to call to initialize it, based on the type of input being assigned:
// copy/move assignment
List& operator=(List rhs)
{
List tmp(std::move(rhs));
std::swap(head, tmp.head);
return *this;
}
*/
};
An operation like L1 = L2 + L3
would have to implement operator+
that takes 2 List
objects as input and returns a new List
object with data (not nodes) that is copied from both L2
and L3
, eg:
template<typename T>
class List
{
...
public:
...
List& operator+=(const List &rhs)
{
if (rhs.head) {
List tmp(rhs);
node **ptr = (tail) ? &(tail->next) : &head;
*ptr = tmp.head; tmp.head = nullptr;
tail = tmp.tail; tmp.tail = nullptr;
size += tmp.size;
}
return *this;
}
// operator+ can be implemented either as a class member
// using *this as the left-hand operand...
List operator+(const List &rhs) const
{
List res(*this);
res += rhs;
return res;
}
};
// Or, operator+ can be implemented as a non-member function...
List operator+(const List &lhs, const List &rhs)
{
List res(lhs);
res += rhs;
return res;
}
The returned List
would be a temporary object, aka an rvalue, which would then be assigned to L1
using the operator=(List&&)
move assignment operator (or, the operator=(List)
assignment operator using the List(List&&)
move constructor).
C++ constructor with rvalue reference
In order to take the rvalue reference, it should be non-const, since the contents of the constructor argument will be moved and typically this is an operation that changes the state of the operand (although not in your particular case):
Circle(Circle&& c){ }
Also, you are seeing a copy elision here:
Circle c3(Circle(4));
so the move constructor doesn't get invoked. This is a standard compiler optimization that may or may not happen. If you were to construct a Circle
like this:
Circle c3(std::move(c1));
then you would invoke the move constructor.
C++11: shortest way to explicitly copy a value to use as an rvalue reference
template<class T>
std::remove_cv_t<T> copy(T& t) {
return t;
}
template<class T>
void copy(T&&)=delete; // block rvalue copy
template<class T, size_t N>
void copy(T(&)[N]) = delete; // we can implement this if we want: see below
will copy any lvalue. On rvalues and non-copyable types (like arrays) it will fail to match or compile.
template<class T>
std::decay_t<T> copy_even_if_rvalue(T&& t) {
return std::forward<T>(t);
}
namespace details {
template<class T, size_t N, size_t...Is>
std::array<T, N> copy_even_if_rvalue(T(&arr)[N], std::index_sequence<Is...>)
{
return {{arr[Is]...}};
}
}
template<class T, size_t N>
std::array<T, N> copy_even_if_rvalue(T(&arr)[N]) {
return details::copy_even_if_rvalue(arr, std::make_index_sequence<N>{} );
}
template<class T, size_t N>
std::array<T, N> copy_even_if_rvalue(T(&&arr)[N]) {
return copy_even_if_rvalue(arr); // forward to lvalue version
}
will copy both rvalues and lvalues. Usually copy
is a smarter operation than copy_even_if_rvalue
in my opinion.
copy_even_if_rvalue
will convert raw C arrays into std::array
s, because that is about the only sensible way to copy them. An =delete
overload would be the other reasonable option.
C++11 - Why compiler does not optimize rvalue reference to const lvalue reference binding?
Think about the following example:
void foo(int) {}
void foo(double) {}
void bar(double x) {
foo(x);
}
int main() {
bar(0);
}
In the above program, foo(double)
will always be called, not foo(int)
. This is because while the argument was originally an int
, this information is irrelevant once you are inside bar
. bar
only sees its own parameter x
, which has type double
regardless of what the original argument type was. Therefore, it calls the overload of foo
that is the best match for the type of the parameter x
.
Your func2
works similarly:
void func2(MyClass &el, const MyClass &c) {
el = c;
}
Here, the expression c
is an lvalue, even though the reference may have been bound to a temporary object at the time of calling. Because of that, the compiler must select the =
operator that takes an lvalue as its right argument.
In order to forward lvalues as lvalues and rvalues as rvalues, overloading with const MyClass&
and MyClass&&
is often used, even though (as you note) it is duplicative. For some suggestions on how to reduce code duplication, see Is There A Way To Remove Duplicate Code While Providing lvalue and rvalue Overloads? and How do I prevent code repeat between rvalue and lvalue member functions?
Copy constructor is being called first instead of only move constructor on passing even R-value reference
An moving assignment (not an assignment initialization) requires a move operator=(strings&&)
. It is not available if you defined own copy constructor. Note, that in situation like this
struct S {
S() {}
S(const S &obj) { std::cout << "Copy ctor" << std::endl;}
S(S &&obj) { std::cout << "Move ctor" << std::endl;}
S& operator=(const S &obj) { std::cout << "Copy =" << std::endl; return *this;}
S& operator=(S &&obj) { std::cout << "Move =" << std::endl; return *this;}
};
int main()
{
S a = S();
a = S();
}
with C++11 compiler the declaration line wouldn't even produce output, because no copy or move did happen, it was elided.
Related Topics
Why Isn't There an Operator[] for a Std::List
Relative Paths Not Working in Xcode C++
Displaying Svg in Opengl Without Intermediate Raster
How to Block Running Two Instances of the Same Program
How to Iterate Over a Std::Tuple in C++ 11
C++ Local Variable Destruction Order
Uses for Anonymous Namespaces in Header Files
Advantages of Classes with Only Static Methods in C++
Is Writing to &Str[0] Buffer (Of a Std:String) Well-Defined Behaviour in C++11
Deprecated Throw-List in C++11
Why Is There No 2-Byte Float and Does an Implementation Already Exist
How to Check That the Passed Iterator Is a Random Access Iterator
Fast Divisibility Tests (By 2,3,4,5,.., 16)
How to Create a C++ Boost Undirected Graph and Traverse It in Depth First Search (Dfs) Order
Can Std::Vector Emplace_Back Copy Construct from an Element of the Vector Itself