Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move constructor seemingly not executed

Tags:

c++

c++11

This is my first experiment with C++0x rvalue references and something strange seems to be going on.

In the code sample below the factory function MakeWindow returns a Window object by value. The caller uses it to initialize a Window object. If I understood correctly, this should invoke the move constructor. In order to detect this I throw an exception there. On top of that I disabled the copy constructor:

#include <iostream>


// Fake WinAPI
typedef void* HWND;
HWND CreateWindow() { return (void*)1; }
void DestroyWindow(HWND) { }
// End WinAPI


// C++ WinAPI Wrapper Library
class Window
{
public:
    Window(HWND inHandle) :
        mHandle(inHandle)
    {
        std::cout << "Window constructor. Handle: " << inHandle << std::endl;
    }

    Window(Window && rhs) :
        mHandle(rhs.mHandle)
    {
        std::cout << "Window move constructor. Handle: " << mHandle << std::endl;
        rhs.mHandle = 0;
        throw 1; // this is my "breakpoint"
    }

    ~Window()
    {
        std::cout << "Window destructor. Handle: " << mHandle << std::endl;
        if (mHandle)
        {
            DestroyWindow(mHandle);
        }
    }

private:
    Window(const Window&);
    Window& operator=(const Window&);

    HWND mHandle;
};


// Factory function
Window MakeWindow()
{
    return Window(CreateWindow());
}


int main()
{

    {
        Window window(MakeWindow());
    }
    std::cout << "Everything is OK." << std::endl;
    return 0;
}

However the code runs fine without this exception being thrown. This is the console output:

Window constructor. Handle: 0x1
Window destructor. Handle: 0x1
Everything is OK.

If I comment out the move constructor then compilation fails with the following errors:

MysteryMove.cpp: In function 'Window MakeWindow()':
MysteryMove.cpp:39:5: error: 'Window::Window(const Window&)' is private
MysteryMove.cpp:49:33: error: within this context
MysteryMove.cpp: In function 'int main()':
MysteryMove.cpp:39:5: error: 'Window::Window(const Window&)' is private
MysteryMove.cpp:57:35: error: within this context
make: *** [all] Error 1

It doesn't seem to make sense. Can anyone explain what is going on?

Update

Thanks to @Philipp I learned that move constructors can also be omitted. This is described in §12.8/34 and footnote 124 of the N3126 draft standard.

It is there also mentioned that RVO is only allowed for non-volatile objects. This means I can get around it writing the factory function like this:

// Factory function
Window MakeWindow()
{
    volatile Window window(CreateWindow());
    return const_cast<Window&&>(window);
}

And indeed it works:

Window constructor. Handle: 0x1
Window move constructor. Handle: 0x1
terminate called after throwing an instance of 'int'
Abort trap
like image 530
StackedCrooked Avatar asked Jan 22 '11 11:01

StackedCrooked


People also ask

Are move constructors automatically generated?

If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.

What does move () do in C ++?

Move Constructor And Semantics: std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.

What is the point of a move constructor?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.

How do I turn off move constructor?

To correct this, remove the move constructor completely. In the case of the class, once a copy constructor is present (user defined), the move is implicitly not generated anyway (move constructor and move assignment operator).


1 Answers

Isn’t it obvious? Your code returns a copy of the local temporary Window:

Window MakeWindow()
{
    return Window(CreateWindow());
}

The compiler will in fact optimize this copy away (via return value optimization) – this is why your move constructor is never actually called – but for correctness a copy constructor must still be present.

like image 168
Konrad Rudolph Avatar answered Oct 18 '22 05:10

Konrad Rudolph