Question

I'm trying to understand move semantics in C++11, and I've written a small piece of code to check which constructors are called when objects are created.

This is the code:

#include <iostream>

using namespace std;

class Bar
{
public:
    int b;
    Bar();
    Bar(const Bar&);
    ~Bar();
};

class Foo
{
public:
    int d;
    Bar* b;
    Foo();
    Foo(const Foo&);
    Foo(Foo&& other);
    ~Foo();
    Foo& operator = (Foo);
};

Foo test();

Foo::Foo()
{
    cout << "Default ctr of foo called\n";
    b = new Bar();
}

Foo::~Foo()
{
    delete b;
}

Foo::Foo(const Foo& other)
{
    cout << "Copy ctr of foo called\n";
    d = other.d;
    b = new Bar();
    b->b = other.b->b;

}

Foo::Foo(Foo&& other)
{
    cout << "Move ctr of foo called\n";
    d = other.d;
    b = other.b;
    other.d = 0;
    other.b = NULL;
}

Foo& Foo::operator = (Foo other)
{
    cout << "Copy assignment of foo called\n";
    d = other.d;
    b = new Bar();
    b->b = other.b->b;
    return *this;
}

Bar::Bar()
{
    b = 1;
}

Bar::~Bar()
{
}

Bar::Bar(const Bar& other)
{
    b = other.b;
}

int main()
{
    Bar b;
    Foo f1;
    Foo f = test();
    Foo f3 = move(test());
//    cout << f1.b->b << "\n";
//    Foo f2(test());


    return 0;
}

Foo test()
{
    Foo f;
    return f;
}

This is the output I am getting :

Default ctr of foo called
Default ctr of foo called
Default ctr of foo called
Move ctr of foo called

I don't quite get why the move constructor is not called for Foo f. Isn't test() an rvalue? Which constructor is being called in this case?

Any help will be greatly appreciated.

Was it helpful?

Solution

What you're seeing here is the effect of copy elision. Copy elision is an optimization specified by the standard that enables the compiler, in a situation where you return an object from the stack, to have the function build the object in the caller's stack space and bypass the copy and move constructors altogether, regardless of side effects. Side effects, in your case, are what the constructor would print to cout.

We can prevent the compiler from using this shortcut by using, for instance, a unique_ptr to create the object on the heap. It's a little more involved, but it gets you the result you expect:

#include <unique_ptr>

Foo test()
{
    unique_ptr<Foo> f(new Foo);
    return move(*f);
}

In this case you need to move the pointee of f because unique_ptr's operator* returns a reference, not a temporary. If you don't move it, it'll call the copy constructor instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top