Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 - I lost a move/copy assignment

Tags:

c++

c++11

I posted this code in another question but I have a new doubt on it:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class X
{
    public:

    std::vector<double> data;

    // Constructor1
    X():
        data(100000) // lots of data
    {
        cout << "X default constructor called";
    }

    // Constructor2
    X(X const& other): // copy constructor
        data(other.data)   // duplicate all that data
    {
        cout << "X copy constructor called";
    }

    // Constructor3
    X(X&& other):  // move constructor
        data(std::move(other.data)) // move the data: no copies
    {
        cout << "X move constructor called";
    }

    X& operator=(X const& other) // copy-assignment
    {
        cout << "X copy assignment called";
        data=other.data; // copy all the data
        return *this;
    }

    X& operator=(X && other) // move-assignment
    {
        cout << "X move assignment called";
        data=std::move(other.data); // move the data: no copies
        return *this;
    }

};

class X2
{
    public:

    std::vector<double> data;

    // Constructor1
    X2():
        data(100000) // lots of data
    {}

    // Constructor2
    X2(X const& other): // copy constructor
        data(other.data)   // duplicate all that data
    {}

    X2& operator=(X const& other) // copy-assignment
    {
        data=other.data; // copy all the data
        return *this;
    }
};

X make_x()
{
    X myNewObject; // Il normale costruttore viene chiamato qui
    myNewObject.data.push_back(22);
    return myNewObject; // Si crea un oggetto temporaneo prima di ritornare con il move constructor perchè myNewObject dev'essere distrutto
}


int main()
{
    X x1 = make_x(); // x1 has a move constructor


    X2 x2 = make_x(); // x2 hasn't a move constructor
}

In the main() lines I would expect the move assignment and the copy assignment to get called... but they don't!

MSVC2012 output is:

X default constructor called X move constructor called X default constructor called X move constructor called

And g++'s one is

X default constructor called X default constructor called

http://liveworkspace.org/code/220erd$2

Where are the assignments?? I thought the first main() line would call a move assignment and the second main() line would call a copy assignment

like image 883
Johnny Pauling Avatar asked Dec 12 '22 16:12

Johnny Pauling


1 Answers

// Constructor2
X2(X const& other): // copy constructor
    data(other.data)   // duplicate all that data
{}

X2& operator=(X const& other) // copy-assignment
{
    data=other.data; // copy all the data
    return *this;
}

First of all, these are not the copy constructor and copy assignment operator for X2 because they take arguments of type X. The first is actually known as a converting constructor, because it can convert from an X to an X2.

int x = 5;

This is not 5 being assigned to x; it is x being initialised with 5. Initialisation, although it looks similar, is not the same as assignment. In fact, no assignment occurs at all in your code, so the move or copy assignment operators will not be used.

We can take a look at what each of the compilers you've given is actually doing:

  1. MSVC

    First, myNewObject is created in make_x. This prints out X default constructor called. Then the return myNewObject; will treat the copy to the return value as a move first, find that there is a move constructor, and invoke it.

    When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

    The return value would then be copied into x1. However, this copy has clearly been elided because we see no X copy constructor called output:

    when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

    Secondly, another myNewObject is created in the second call to make_x. This again prints out X default constructor called. Then the same move happens when doing return myNewObject. The construction of x2 from the return value doesn't output anything because its constructor that takes an X doesn't do any output.

  2. GCC

    First, myNewObject is created in make_x, just as with MSVC. This prints out X default constructor called.

    Now GCC does an extra optimization that MSVC isn't doing. It realises that it might as well not bother moving from myNewObject to the return value and instead just construct it directly in the return value's place:

    in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

    Then the same elision caused by constructing x1 from a temporary object is performed as was in MSVC.

    The second call to make_x occurs in exactly the same way as with the first, except now x2 is constructed by the converting constructor that takes an X. This, of course, doesn't output anything.

like image 186
Joseph Mansfield Avatar answered Dec 27 '22 00:12

Joseph Mansfield