I tried the following code:
#include <iostream>
struct test{
test(){}
test(test const &){
std::cout<<__LINE__<<"\n";
}
test(test&&){
std::cout<<__LINE__<<"\n";
}
};
#include <vector>
#include <utility> // std::move
int main(){
auto&& tmp = test();
std::vector<test> v;
v.push_back(tmp);
std::cout<<__LINE__<<"\n";
v.push_back(std::move(tmp));
return 0;
}
The vs2013 compiler output:
6 // copy
18
9 // move
9 // move
The g++ and clang++ output:
6 // copy
18
9 // move
6 // copy
My problems are:
Is the type of tmp test&&? Is tmp a rvalue?
If the type of tmp is test&&, why didn't the first push_back use move constructor?
Where did the last output come from? Why did vs2013 and g++ output different results?
Thanks.
Answer for 3rd problem: It comes from the reallocation as commented by andrew.punnett.
Is the type of
tmp
test&&
?
Yes and no. tmp
is an rvalue reference variable of type test&&
, but the identifier tmp
taken as an expression has type test
and value category lvalue. The &
is never part of the type of an expression.
Is
tmp
a rvalue?
No. Any use of an identifier is an lvalue expression, even the name of an rvalue reference. Rvalue reference variables are accessed essentially identically to lvalue reference variables; only decltype(tmp)
can tell the difference. (Often you would use decltype((tmp))
to avoid telling the difference.)
If the type of tmp is test&&, why didn't the first push_back use move constructor?
Because the name of an rvalue reference is still an lvalue. To get an rvalue expression, use move(tmp)
.
Where did the last output come from? Why did vs2013 and g++ output different results?
Clang and GCC only made room for one object in the vector
by default. When you added the second object, the vector storage was reallocated causing the objects to be copied. Why weren't they moved? Because the move constructor is not noexcept
, so if it threw an exception, it would be impossible to undo the reallocation.
As for the two moves by MSVC, there are two possibilities, which you can distinguish by experimentation — I don't have a copy handy.
noexcept
and called it to perform relocation anyway. This would be a bug, but in this case it's covering up a common mistake.If you replace vector
with deque
, you won't see any more copies, because deque
is not allowed to assume copyability.
Visual Studio do not yet support the noexcept keyword and is likely not compliant to the exception safety of push_back. Also, the additional output is a result of the difference on capacity computation on grow.
#include <iostream>
#include <vector>
#include <utility> // std::move
struct Except{
Except(){}
Except(Except const &) {
std::cout<< "COPY\n";
}
Except(Except&&) {
std::cout<< "MOVE\n";
}
};
struct NoExcept{
NoExcept(){}
NoExcept(NoExcept const &) noexcept {
std::cout<< "COPY\n";
}
NoExcept(NoExcept&&) noexcept {
std::cout<< "MOVE\n";
}
};
template <typename T> void Test( char const *title,int reserve = 0) {
auto&& tmp = T();
std::cout<< title <<"\n";
std::vector<T> v;
v.reserve(reserve);
std::cout<< "LVALUE REF ";
v.push_back(tmp);
std::cout<< "RVALUE REF ";
v.push_back(std::move(tmp));
std::cout<< "---\n\n";
}
int main(){
Test<Except>( "Except class without reserve" );
Test<Except>( "Except class with reserve", 10 );
Test<NoExcept>( "NoExcept class without reserve" );
Test<NoExcept>( "NoExcept class with reserve", 10 );
}
And the result within clang :
Except class without reserve
LVALUE REF COPY
RVALUE REF MOVE
COPY
---
Except class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---
NoExcept class without reserve
LVALUE REF COPY
RVALUE REF MOVE
MOVE
---
NoExcept class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With