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?
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
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.
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.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With