【问题标题】:C++ constructor calling orderC++构造函数调用顺序
【发布时间】:2011-10-22 15:17:11
【问题描述】:

当我初始化我的类的一个对象时,默认构造函数和复制构造函数都会被调用。

class A
{
public:
   A (string s) { str = string (s); cout << "default" << endl; }
   A (int n) { cout << "A (int n)" << endl; }
   A (string s, int n) { cout << "A (string s, int n)" << endl; }
   A (int n2, string s2) { cout << "A (int n2, string s2)" << endl; }
   A (const A& a) { str = a.str; cout << "copy" << endl; }

   inline void printA () { cout << str << endl; }

   string str;
};


int main (void)
{
   A a_1 = A ("con 1");
   cout << endl;

   A a_2 = "con 2";
   cout << endl;

   A a_3 = A (4);

   A a_4 = A ("a_4", 10);
   cout << endl;

   A a_5 = A (11, "a_5");
   cout << endl;

   cin.get();
   return 0;
}

结果:

默认 复制 默认 A (int n) A (字符串 s, int n) 复制 A (int n2, 字符串 s2) 复制

为什么a_1a_3a_4 同时调用默认构造函数和复制构造函数? A_3 也有一个参数,但它不需要复制构造函数。

【问题讨论】:

  • 为什么要添加默认构造函数?
  • @curiousguy:我想在这里更清楚地描述问题。我可以这样做:A () { cout &lt;&lt; "default" &lt;&lt; endl; } 并将当前默认构造函数更改为:A (string s) { str = string (s); cout &lt;&lt; "A (string s)" &lt;&lt; endl; } 以获得更好的调试信息。此外,如果您将 Foo() 设为私有,则此构造函数在类外将变得不可用。然后,explicit 关键字做了我不记得的事情:) AFAIK 就是这样。
  • @MartinBerger 请编辑“为什么 A_1 调用默认值”。
  • @Martin:你不需要默认构造函数,我只是说输出“默认”的构造函数不是默认构造函数,因此调用默认构造函数的说法是只是错了。

标签: c++ constructor copy-constructor


【解决方案1】:

为避免多余的复制构造函数调用,去掉=,并使用此语法直接调用所需的构造函数:

A a_1("con 1");
A a_2("con 2");
A a_3(4);
A a_4("a_4", 10);
A a_5(11, "a_5");

【讨论】:

  • 这里没有默认的构造函数调用。
  • 顺便说一句,“这种语法直接调用所需的构造函数”被称为直接-初始化。
【解决方案2】:

A a_1 = A ("con 1");

通过调用将字符串作为参数的构造函数构造一个临时对象,因为传递的类型是const char *,编译器必须先执行隐式转换为string(),然后使用这个临时对象复制构造一个新的a_1 对象。
由于有一个额外的隐式转换,编译器无法优化它,需要调用复制构造函数。
根据 cmets 和进一步的研究,我怀疑(以上斜体)推理是否正确。

@David 在他的 cmets 中建议:
不能省略副本不是因为隐式转换,而是因为转换是显式的。也就是说,编译器无法对其进行优化,因为代码明确要求创建临时和复制构造。

但是,@David 和我都无法通过标准引用来证实它。

A a_2 = "con 2";

通过调用以字符串为参数的适当构造函数来构造对象a_2

A a_3 = A (4);

案例 1 中的注释也适用于此:
理想情况下应该通过调用以整数作为参数的构造函数来构造一个临时对象,然后使用这个临时对象复制构造一个新的a_3对象,如案例1,但编译器可以通过调用构造函数来优化并直接构造该对象将整数作为一个可用。

A a_4 = A ("a_4", 10);

通过调用以字符串和整数作为参数的构造函数构造一个临时对象,然后使用这个临时对象复制构造一个新的a_4对象。

A a_5 = A (11, "a_5");

通过调用以整数和字符串为参数的构造函数构造一个临时对象,然后使用这个临时对象复制构造一个新的a_5对象。

请注意,您没有为您的类定义默认构造函数。

您可以通过避免创建临时对象,然后在上述情况下不使用赋值来复制构造对象,以更有效的方式实现同​​样的目的(=)。

A a_1("con 1");
A a_2("con 2");
A a_3(4);
A a_4("a_4", 10);
A a_5(11, "a_5");

我最初的回答是试图解释这种行为,但是当我在 Ideone 上的 gcc-4.3.4 上编译它时,我发现 gcc 足够智能,可以优化复制构造函数调用。没有一种情况会调用复制构造函数。

我得出的结论是,在这种情况下,每个编译器都可以或不能根据其智能优化复制构造函数调用,而标准不要求编译器执行此类优化,但每个编译器都会根据其能力评估此类表达式。

如果我对此有误,请随意给我添加推理评论。

【讨论】:

  • 这是错误的。 a_3中没有复制构造;鉴于复制构造发生在所有其他情况下,OP 正在询问为什么会这样。
  • @Gorpik:谢谢纠正。对 cntrl+c cntrl+v 有点忘乎所以 :)
  • @MartinBerger 编译器也没有任何优化要求。
  • 第一种情况最糟糕,不能省略副本不是因为隐式转换,而是因为转换是显式。也就是说,编译器无法优化它,因为代码显式请求创建临时和复制构造。将其与转换实际上是 implicit 并且编译器将其优化掉的第二种情况进行比较。 1中的实际顺序是:从文字到字符串的转换,从字符串构造临时,最后是复制构造。您的描述缺少中间步骤
  • @DavidRodríguez-dribeas:感谢您的详细说明,标准是否对此有任何规定?我相信编译器可以根据他们的能力进行优化,因为 gcc(Ideone link in answer)只是优化了所有的复制构造函数调用,而 OP 声称的 msvc2010 提供了上述输出。
【解决方案3】:

原因是您正在进行隐式转换。当您构造a_1 时,您使用的是const char*,它被隐式转换为std::string,它被输入A 构造函数,然后复制构造。构造a_3时,不涉及隐式转换,因此允许编译器跳过复制构造函数,直接用int构造a_3

【讨论】:

  • 好的。感谢您在 Als 帖子中做出贡献。 +1
  • 你能备份这个答案吗?
  • @curiousguy:我认为标准并没有明确禁止在案例 3 中跳过复制构造函数;但它禁止链接两个隐式转换。此禁令允许某些代理类正常工作。编译器可能很难知道跳过复制构造函数是否会导致违反该禁令,所以它只是不这样做。
  • "但它禁止链接两个隐式转换"它没有。
  • @curiousguy:不完全是两个隐式转换,是的,但它禁止两个用户定义的隐式转换。请参阅 C++11 中的 13.3.3.1。
【解决方案4】:

简答

这里没有人知道。 您应该询问编译器编写者。

或者换个编译器。

这种不一致的行为没有根本原因。

详细解答

1) 显示的编译器行为不一致

2)只有编译器作者才能回答为什么编译器行为不一致的问题 - 甚至他也可能没有,您需要浏览源代码

3) 所以,最好的非答案是不要浪费太多时间试图理清编译器行为不一致的原因

4) 如果优化对您很重要,请切换编译器

【讨论】:

    猜你喜欢
    • 2012-08-22
    • 2014-01-28
    • 1970-01-01
    • 2012-04-10
    • 2013-06-24
    • 2010-12-25
    • 2010-10-13
    • 2016-03-18
    相关资源
    最近更新 更多