6

I have the following simplified code example:

#include <algorithm>
#include <iostream>

using namespace std;

class ShouldBeMovedWhenSwapped
{
public:
//  ShouldBeMovedWhenSwapped() = default;
//  ShouldBeMovedWhenSwapped(ShouldBeMovedWhenSwapped&&) = default;
//  ShouldBeMovedWhenSwapped(const ShouldBeMovedWhenSwapped&) = default;
//  ShouldBeMovedWhenSwapped& operator=(ShouldBeMovedWhenSwapped&&) = default;

    struct MoveTester
    {
        MoveTester() {}
        MoveTester(const MoveTester&) { cout << "tester copied " << endl; }
        MoveTester(MoveTester&&) { cout << "tester moved " << endl; }
        MoveTester& operator=(MoveTester) { cout << "tester emplaced" << endl; return *this; } // must be declared if move declared
    };

    MoveTester tester;
};

int main()
{
    ShouldBeMovedWhenSwapped a;
    ShouldBeMovedWhenSwapped b;
    std::swap(a,b);
    return 0;
}

I'm using MinGW, while running 'gcc --version' i get gcc 4.7.2

EDIT: for the first question see the comments in the question. It appears to be a bug in gcc.

The output of the code depends on which constructors are commented out. But i don't understand why the differences occur. What is the reason behind each output?

// Everything commented out
tester moved 
tester copied <---- why not moved?
tester emplaced
tester copied <---- why not moved?
tester emplaced

// Nothing commented out
tester moved
tester moved
tester emplaced
tester moved
tester emplaced

// Move constructor commented out
tester copied
tester moved
tester emplaced
tester moved
tester emplaced

For my second question (which was why i started this test) - Let's say i have a real case with a large vector instead of the class MoveTester, how can i be sure the vector is moved instead of being copied in such cases?

user1708860
  • 1,683
  • 13
  • 32

1 Answers1

4

The first part of the problem is an outdated compiler, but there's another one: you declared MoveTester::operator= in suboptimal way - it takes its argument by value, so a copy/move constructor is invoked one extra time. Try this version of MoveTester:

struct MoveTester
{
    MoveTester() {}
    MoveTester(const MoveTester&) { cout << "tester copied " << endl; }
    MoveTester(MoveTester&&) { cout << "tester moved " << endl; }
    MoveTester& operator=(const MoveTester&) { cout << "tester copy assignment" << endl; return *this; } // must be declared if move declared
    MoveTester& operator=(MoveTester&&) { cout << "tester move assignment" << endl; return *this; } // must be declared if move declared
};

I'm getting the following output:

tester moved 
tester move assignment
tester move assignment

Perhaps you'll get something similar even with GCC 4.7.


Regarding your second question, the move constructor of std::vector is guaranteed by standard to have constant time complexity. The question is whether the compiler obeys the standard. I believe the only way to make sure is to debug or to profile your code.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • I declared it like that because of the 'copy and swap idiom' http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom But i indeed get that exact output if i change the operator – user1708860 Dec 21 '15 at 21:06
  • Well, now i'm even more confused, this seems to be the output i wanted. According to the linked question, this version should have resulted in an optimal code... – user1708860 Dec 21 '15 at 21:11
  • @user1708860 there's no contradiction here. Your version indeed would employ copy&swap idiom, so in `operator=` you would just swap contents - not much work. Just update the compiler and you'll be able to use the original version. – Anton Savin Dec 21 '15 at 21:14
  • 3
    @user1708860 now you understand why the copy-and-swap idiom is usually sub-optimal in performance terms. Its benefits are ease of implementation, and ease of strong exception safety. – M.M Dec 21 '15 at 21:18