为什么调用复制构造函数而不是移动构造函数?

8
请看下面的示例代码:
   #include <iostream>

    struct Foo {
        Foo() { std::cout << "Default!\n"; }
        Foo(const Foo& foo) { std::cout << "Copy!\n"; }
        Foo(Foo&& foo) { std::cout << "Move!\n"; }
    };

    struct Bar {
        Foo foo;
        Bar() {}
        Bar(Bar &that) : foo(that.foo) {}
        Bar(Bar &&that) : foo(std::move(that.foo)) {}
    };

    Bar f() {
        Bar bar;
        return bar;
    }

    int main() {
        Bar bar(f());
    }

我希望这段代码的输出结果应该是:
Default!
Move!

但我得到的是:
Default!
Copy!

我看不出为什么会调用复制构造函数而不是移动构造函数。如果我在struct Bar的复制构造函数声明中,在Bar &that前面加上关键字const,就能得到正确的结果。我知道对于许多情况来说,使用const左值引用比只使用左值引用更好,但我只想知道为什么会发生这种情况。
为什么在这个例子中,尽管f()的返回值应该被视为prvalue,但Bar &优先于Bar &&?为什么关键字const解决了这个问题?const确实解决了问题吗?这与RVO(返回值优化)有关吗?还是这只是编译器的bug?
我在Visual C++ November 2012 CTP上测试了这个例子。
我在这里找到了一个类似的问题: 复制构造函数被调用而不是移动构造函数 但我仍然无法理解为什么。

有人可以帮我吗?


@EmilioGaravaglia 我已经在问题中提到了。我想知道的是为什么“const”会有如此大的差异。 - Junekey Jeon
在函数f中,从本地变量barf的返回值有一个移动操作,而在主函数中,从构造函数参数到bar也有一个移动操作。 - Kerrek SB
2
你为什么使用这么老的版本?它实际上没有任何优点——它从未发布过,也从未被授权用于生产代码,因此你甚至不能以工具链稳定性为借口。 - Ben Voigt
@BenVoigt 因为我没有使用更高版本的许可证...但是我想尝试一下C++11/14(特别是可变参数模板)...也许我应该转向其他编译器。顺便问一下,您是不是意味着我不应该使用CTP来开发商业产品,因为存在法律问题?还是因为它的不稳定性和潜在的大量错误,就像这样?无论如何,非常感谢您。 - Junekey Jeon
@JunekeyJeon:两者都有。您需要检查实际许可证以充分了解法律问题,但是该版本的公告明确表示:“这是客户技术预览版,不带有“Go Live”许可证”。据我所知,对于任何CTP,您都缺少运行时库重新分发的通常权限,因此如果您使用Visual C++ CTP构建的二进制文件进行发布,则可能会对版权侵权承担责任。 - Ben Voigt
显示剩余4条评论
3个回答

5
这只是Visual C++的一般性不兼容问题,它允许将非const左值引用绑定到临时对象。这违反了语言规则,但在被发现之前已经存在了很长时间,因此现在有一些代码依赖于该错误,如果正确修复,这些代码将会出错。
这种行为错误地允许使用非const拷贝构造函数,并结合Visual C++版本中不完整的右值引用支持,显然会导致选择错误的重载。
如果您想要使用C++11/C++14功能,最好保持最新的Visual C++版本。

3

哇,当我使用...

  1. Visual Studio的Debug编译时,我看到了"Default! Copy!"。
  2. Visual Studio的Release编译时,我看到了"Default!"。
  3. 如果您将Bar(Bar &that)更改为Bar(const Bar &that),那么就会显示"Default! Move!"
  4. 令人震惊的是,如果您将Bar(Bar &that)Bar(Bar &&that)的顺序交换(使移动构造函数先定义),那么实际上您将看到"Default! Move!"

1
哇!我也亲自测试了1、2和3,但4真的很令人惊讶!谢谢。那么你有关于这种情况发生的原因有任何想法吗? - Junekey Jeon
2
好的,第四点基本上可以确定这是编译器的错误。 - T.C.
公平起见,f() 应该返回 std::move(bar) - Mo Beigi
4
不应该这样做。事实上,这是一种劣化操作,因为它会抑制(N)RVO。 - T.C.

0

你的问题可能已经在这里得到了解答。

临时对象不能绑定到非const引用。复制构造函数必须采用对const对象的引用,才能够复制临时对象。

另一件事是,临时对象不应该被修改,因为它们很快就会被销毁。持有临时对象的引用会导致由于粗心而对不存在的对象进行潜在的修改。


那句话与正在观察的行为无关。 - T.C.
1
@T.C.:这句话与此有关。拷贝构造函数不仅应该是一个更差的匹配,甚至不应该成为候选集的一部分。 - Ben Voigt
1
@BenVoigt 当然,但这并没有解释任何事情。问题是“为什么被称为这个名字?”,而不是“为什么不被称为这个名字?” - T.C.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接