Yang
Yang

Reputation: 777

C++11 move semantics and Microsoft Visual C++ compiling optimization

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

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

Answers (1)

Ben Voigt
Ben Voigt

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. If T 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 type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.

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

Related Questions