【问题标题】:Move constructor seemingly not executed移动构造函数似乎没有执行
【发布时间】:2011-01-22 11:45:13
【问题描述】:

这是我对 C++0x 右值引用的第一次实验,似乎正在发生一些奇怪的事情。

在下面的代码示例中,工厂函数MakeWindow 按值返回一个 Window 对象。调用者使用它来初始化一个 Window 对象。如果我理解正确,这应该调用移动构造函数。为了检测到这一点,我在那里抛出了一个异常。最重要的是,我禁用了复制构造函数:

#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;
}

但是,代码运行良好,没有抛出此异常。这是控制台输出:

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

如果我注释掉移动构造函数,则编译失败并出现以下错误:

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

这似乎没有意义。谁能解释一下是怎么回事?

更新

感谢@Philipp,我了解到移动构造函数也可以省略。这在 §12.8/34 和 N3126 draft standard 的脚注 124 中有描述。

这里还提到 RVO 只允许用于非易失性对象。这意味着我可以像这样编写工厂函数来绕过它:

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

确实有效:

Window constructor. Handle: 0x1
Window move constructor. Handle: 0x1
terminate called after throwing an instance of 'int'
Abort trap

【问题讨论】:

  • 试试g++ -fno-elide-constructors

标签: c++ c++11


【解决方案1】:

这不是很明显吗?您的代码返回本地临时Window副本

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

编译器实际上会优化这个副本(通过返回值优化)——这就是为什么你的移动构造函数从未真正被调用——但为了正确起见,复制构造函数必须仍然存在。

【讨论】:

  • 我怀疑。但是,我不知道确切的规则,但我希望在这种情况下不允许使用 RVO,因为它会极大地改变程序流程。
  • @Stacked:标准的显式允许省略复制(我怀疑是移动)构造函数调用。但是,这不允许用于任何其他功能。
  • 但是移动构造函数也负责使源对象无效。这个可以自动生成吗?知道哪些成员需要设置为空值需要一些人工智能和有根据的猜测......
  • @Stacked:如果省略了移动构造函数,则源对象和目标对象相同,因此不必使任何对象无效。请参阅 N316 中的 §12.8/34 和脚注 124。
  • @Konrad: §12.8/10 说“如果类定义没有显式声明移动构造函数,当且仅当 X 没有具有用户声明的复制构造函数,并且移动构造函数不会被隐式定义为已删除。”对于复制构造函数(第 12.8/8 节)类似:“如果类定义没有显式声明复制构造函数并且没有用户声明的移动构造函数,则复制构造函数被隐式声明为默认值”。所以我认为在这种情况下你不必声明复制构造函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-19
  • 1970-01-01
  • 1970-01-01
  • 2018-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多