2

I made a test program to test some of the move semantics, see below.

struct testcd
{
    int x;

    testcd(int i) : x(i) { std::cout << "constructor for " << i << "\n"; }
    ~testcd() { std::cout << "destructor for " << x << "\n"; }

    testcd(const testcd& cd) { std::cout << "copy constructor\n"; x = cd.x; }
    testcd(testcd&& cd) { std::cout << "move constructor\n"; x = cd.x; }

    testcd& operator=(const testcd& cd) { std::cout << "copy operator\n"; x = cd.x; return *this; }
    testcd& operator=(testcd&& cd) { std::cout << "move operator\n"; x = cd.x; return *this; }
};

testcd TestCD()
{
    std::cout << "- enter" << std::endl;

    std::vector <testcd> vec;
    std::cout << "- after empty vector" << std::endl;

    {
        testcd s(2);
        vec.emplace_back(s);
    }
    std::cout << "- after emplace back" << std::endl; 

    {
        testcd t(1);
        vec.push_back(t);
    }
    std::cout << "- after push back" << std::endl;

    for (auto&& t : vec)
    {
        std::cout << t.x << " ";
    }
    std::cout << "\n- after for &&" << std::endl;


    testcd c1(10);
    return c1;

}

int main()
{
    {
        auto cd = TestCD();
        std::cout << cd.x << std::endl;
    }

    _getch();
    return 0;
}

The output is here:

- enter
- after empty vector
constructor for 2
copy constructor
destructor for 2
- after emplace back
constructor for 1
copy constructor
copy constructor
destructor for 2
destructor for 1
- after push back
2 1
- after for &&
constructor for 10
destructor for 2
destructor for 1
10
destructor for 10

There are a couple of things which are still puzzling me:

  1. push_back and emplace_back seem very similar, the copy constructor is used for both. Is there actually a difference between the 2?

  2. It seems that when pushing to a container (std::vector), no move semantics are called. The only way to do this is to force this by calling vec.push_back(std::move(t)); The compiler can see that the temporary value goes out of scope without being used.

  3. Returning seems to perform as expected (sort of). Without optimizations, the move constructor is called. With optimizations, it seems that there's only one object created, which is shared by both methods, without moving or copying anything. However, if I return using std::move, then I destroy the Release optimization and there's an extra call to the move constructor+constructor+destructor.

So I am wondering: why isn't the language consistent: pushing to the vector requires that I call move(), returning requires me NOT to call move(). It seems that I need to test this with every compiler and every use case so that I am sure I write simple optimal code, and it's very misleading to the beginners.

Code was compiled with VS2017 C++17, tested on both Release and Debug.

AndreiM
  • 815
  • 9
  • 17

0 Answers0