构造函数还是复制构造函数?

8
在书籍《泛型编程与STL》(中文版)中,有这样一句话:

X x = X() 会调用拷贝构造函数。

这对我来说有些奇怪。我写了一个测试程序,像这样:
#include <iostream>

class Test {

public:

    Test() {
        std::cout << "This is ctor\n";
    }

    Test(const Test&) {
        std::cout << "This is copy-ctor\n";
    }

};

int main(int argc, char** argv)
{

    Test t = Test();
    return 0;
}

输出是"This is ctor"。好的,现在我有点困惑了,哪个是正确的?
3个回答

9
通常情况下,临时对象会被默认构造,然后复制构造函数会被调用以将其复制到您的对象 t 中。

但是,在实践中,即使它具有副作用(控制台输出),复制也可以被优化掉:

[n3290: 8.5/16]: [..] 在某些情况下,实现可以通过直接在正在初始化的对象中构造中间结果来消除这种直接初始化中固有的复制;请参见12.2、12.8。

并且(与相同条款中给出的示例相结合):

[n3290: 12.2/2]: [..] 实现可能使用一个临时变量来构造 X(2),然后使用 X 的复制构造函数将其传递给 f();或者,X(2) 可能在用于保存参数的空间中构造。[..]

但是,复制构造函数仍然必须存在,即使它可能不会被调用。

无论如何,如果您关闭了优化编译(或者使用 GCC,可能需要使用 -fno-elide-constructors),您会看到:

This is ctor
This is copy-ctor

2
在gcc中,你可能需要使用-fno-elide-constructors,因为即使是-O0也无法防止省略。 - Kerrek SB
即使复制不是*平凡的(trivial)*,复制也可以被省略,通过在本地变量的位置构造临时变量来省略复制。对象或复制的复杂性与该优化无关。 - David Rodríguez - dribeas
现在等一下。你不是那个前几天非常生气的人吗?对于那些只用两句话开始回答,然后逐步扩展的人?:) - sbi
@sbi:啊,但是我的答案即使在最简单的形式下也是正确的。如果我没记错的话,前几天我被一个回答搞烦了,它的开头基本上是“答案即将到来...” - Lightness Races in Orbit
@Tomalak:嗯,我指的是我问这个问题时所指的那一个。:) - sbi
显示剩余3条评论

4

理论上,X x = X() 会调用默认构造函数创建一个临时对象,并使用复制构造函数将其复制到 x 中。

实际上,编译器可以跳过复制构造部分,直接默认构造 x (尽管,正如 David 在他的评论中指出的那样,仍需要在语法上访问复制构造函数)。大多数编译器至少在启用优化时这样做。


4
需要注意的是,即使复制被省略,拷贝构造函数也必须可用。换句话说,如果不可访问,编译器会拒绝该行并报错。 - David Rodríguez - dribeas
@David:你说得对。我已经将这个纳入了我的答案中。谢谢你提出这个问题。 - sbi

2
这是一种情况,其中 返回值优化(RVO)(也称为复制省略)的形式可以在优化方面提供很大帮助。链接的维基百科页面有一个非常好的解释。

3
我不认为那完全正确。这里没有调用任何返回X的函数(记住构造函数没有返回值)。这是复制省略,它是一个相关但不同的概念。 - Kerrek SB
我同意。这不是严格的RVO(虽然它是相关的)。 - Lightness Races in Orbit
@Tomak:这也不完全是错误的。这是一种“那种”优化的变体。+1以抵消负投票。 - Prasoon Saurav
1
@Prasoon:平衡计数是错误的。你的工作不是剥夺其他人投票的权利。 - Lightness Races in Orbit
即使RVO是一个合适的术语,但这个答案并没有真正解释OP代码中发生了什么。仅仅因为这个原因,它就不是一个好的回答。 - Lightness Races in Orbit
@Goz:更好,是的。这里发生的是复制省略;RVO是一种特定的复制省略形式,与此没有严格的关系。 - Lightness Races in Orbit

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