Reputation: 777
Let's consider following class template of custom array in Microsoft Visual C++ (Microsoft Visual Studio 2012 RC, version 11.0.50522.1 RCREL).
/*C++11 switch-on*/
#include <iostream>
template <typename element, unsigned int size>
class array
{
private:
element data[size];
public:
array(){}
~array(){}
array(const array & other)(){}
element & operator [](unsigned int i)
{
if(i<size)
return data[i];
else
throw std::runtime_error("Out of boundary");
}
}
Note that constructor, destructor and copy constructor are defined to do nothing. A trivial printing function is defined as following
/*printing*/
template <typename element, unsigned int size>
void print(test::array<element, size> & content)
{
unsigned int i=0;
for(std::cout<<"["<<content[i++];i<size;std::cout<<content[i++])
std::cout<<",";
std::cout<<"]"<<std::endl;
}
When program runs the following main
int main(int argc, char * argv[])
{
array<int, 3> a;
/* uniform initialization is not supported yet
* so we bother iterating to assign to initialize
* a to [1,2,3]
*/
for(int i=0;i<3;i++)
a[i]=i+1;
/*copy*/
auto b=a;
/*move*/
auto c=std::move(a);
/*change in a*/
a[0]=0;
print<int, 3>(a);
print<int, 3>(b);
print<int, 3>(c);
return 0;
}
the outputs turn out to be different depending on compiling optimization. Particularly, if I compile and run
with /Od switch on
a=[0,2,3]
b=[1470797225,-2,9185596]
c=[0,2620008,9186761]
with /O1, /O2 or /Ox switch on
a=[0,2,3]
b=[0,2,3]
c=[0,2,3]
Now I understand that
But I don't understand why a, b and c are all equal with optimization switch on. I might think that Microsoft C++ compiler replaces copy constructor that does nothing with one that does move, but I'm just not sure about it.
Upvotes: 2
Views: 985
Reputation: 283614
Section [conv.lval]
of the Standard decrees that:
A glvalue of a non-function, non-array type
T
can be converted to a prvalue. IfT
is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of typeT
and is not an object of a type derived fromT
, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. IfT
is a non-class type, the type of the prvalue is the cv-unqualified version ofT
. Otherwise, the type of the prvalue isT
.
Inside print
, the expression cout << content[i++]
uses an lvalue-to-rvalue conversion. When you call print(b)
or print(c)
, the conversion takes place on an object that has never been initialized, so you have undefined behavior.
Trying to characterize undefined behavior is an exercise in futility.
NOTE: Objects b
and c
are initialized by the copy constructor. The copy constructor does not initialize the subobject b.content
or c.content
, meaning that these arrays, and all their member elements, are formally uninitialized.
The code doesn't actually move from a
when initializing c
. std::move
creates an rvalue-reference, which makes moving possible, but there is no matching constructor accepting an rvalue-reference, so the copy constructor is used, a
is copied and not moved.
Upvotes: 6