为什么复制构造函数在对象传递给函数时没有调用[复制]

时间:2022-09-06 00:21:04

This question already has an answer here:

这个问题在这里已有答案:

I have the following code:

我有以下代码:

#include <iostream>
using namespace std;

  class foo
  {
      public:
          foo(int a, int b) :
              a_(a), b_(b)
          { cout << "regular const" << endl; }

          foo(const foo& f) :
              a_(f.a_), b_(f.b_)
          { cout << "copy const" << endl; }

          foo& operator=(const foo& rhs)
          {cout << "copy= const" << endl; a_ = rhs.a_; b_ = rhs.b_; return *this; }

          int a_, b_;
  };

  void bar(foo f)
  { }

  int main()
  {
      foo f1(10, 20);
      cout << "------" << endl;

      bar(f1);
      cout << "------" << endl;

      bar(foo(11, 22));            // line 29
      cout << "------" << endl;

      bar({13, 23});               // line 32
      cout << "------" << endl;    

  }

I get the following output:

我得到以下输出:

$ ./a.out
regular const
------
copy const
------
regular const
------
regular const
------

For line 29 and line 32, I was expecting a temp object to be created in main (invoking regular constructor) and then a copy constructor being invoked when passed to bar(). From the output I see the compiler doing some optimization, and guessing maybe just creating the object on the stack when calling bar() and only regular constructor being invoked. Can someone please help me understand what type of optimization is being done or what is happening under the hood.

对于第29行和第32行,我期望在main中创建一个临时对象(调用常规构造函数),然后在传递给bar()时调用一个复制构造函数。从输出中我看到编译器做了一些优化,猜测可能只是在调用bar()时只在堆栈上创建对象而只调用常规构造函数。有人可以帮助我了解正在进行的优化类型或发生了什么。

Is calling bar() using lines 29 and 32 equivalent as far as generated code? I understand line 29 is more readable.

是否使用与生成代码等效的29和32行调用bar()?我理解第29行更具可读性。

I changed bar() as follows:

我更改了bar()如下:

 void bar(const foo& f)
 { }

I get the same output for lines 29 and 32. In this case where is the object being created?

对于第29行和第32行,我得到相同的输出。在这种情况下,对象是在哪里创建的?

Thank you, Ahmed.

艾哈迈德,谢谢你。

1 个解决方案

#1


3  

In line 32,

在第32行,

bar({13, 23});

the parameter is initialized per copy-list-initialization - no intermediate temporary is created, not even theoretically.

每个copy-list-initialization初始化参数 - 没有创建中间临时,甚至理论上也没有。

bar(foo(11, 22));

Here, copy elision is involved. The compiler is allowed to elide the temporary, that is, construct the object directly into the parameter:

这里涉及复制省略。允许编译器忽略临时,即将对象直接构造到参数中:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
[…]
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

复制/移动操作的省略,称为复制省略,在以下情况下允许(可以组合以消除多个副本):[...] - 当一个未绑定到引用(12.2)的临时类对象时复制/移动到具有相同cv-unqualified类型的类对象,通过将临时对象直接构造到省略的复制/移动的目标中,可以省略复制/移动操作

If you want to see the output without any elided copies or moves, use -fno-elide-constructors with GCC. It will produce the output you expect. At least for line 32.

如果要查看输出而没有任何省略的副本或移动,请在GCC中使用-fno-elide-constructors。它会产生你期望的输出。至少对于第32行。

Now to the second version of bar:

现在到第二个版本吧:

void bar(const foo& f)

In line 29, a temporary is created and initialized from the braced-init-list. In line 32, the reference is bound to the temporary argument. No optimizations or elisions are involved.

在第29行,从braced-init-list创建并初始化临时。在第32行中,引用绑定到临时参数。不涉及任何优化或细则。

bar({13, 23});    // A temporary object is created as if by foo{13, 23},
                  // and the reference is bound to it

bar(foo(11, 22)); // The reference is bound to the temporary.
                  // The lifetime of the temporary has been extended.

#1


3  

In line 32,

在第32行,

bar({13, 23});

the parameter is initialized per copy-list-initialization - no intermediate temporary is created, not even theoretically.

每个copy-list-initialization初始化参数 - 没有创建中间临时,甚至理论上也没有。

bar(foo(11, 22));

Here, copy elision is involved. The compiler is allowed to elide the temporary, that is, construct the object directly into the parameter:

这里涉及复制省略。允许编译器忽略临时,即将对象直接构造到参数中:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
[…]
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

复制/移动操作的省略,称为复制省略,在以下情况下允许(可以组合以消除多个副本):[...] - 当一个未绑定到引用(12.2)的临时类对象时复制/移动到具有相同cv-unqualified类型的类对象,通过将临时对象直接构造到省略的复制/移动的目标中,可以省略复制/移动操作

If you want to see the output without any elided copies or moves, use -fno-elide-constructors with GCC. It will produce the output you expect. At least for line 32.

如果要查看输出而没有任何省略的副本或移动,请在GCC中使用-fno-elide-constructors。它会产生你期望的输出。至少对于第32行。

Now to the second version of bar:

现在到第二个版本吧:

void bar(const foo& f)

In line 29, a temporary is created and initialized from the braced-init-list. In line 32, the reference is bound to the temporary argument. No optimizations or elisions are involved.

在第29行,从braced-init-list创建并初始化临时。在第32行中,引用绑定到临时参数。不涉及任何优化或细则。

bar({13, 23});    // A temporary object is created as if by foo{13, 23},
                  // and the reference is bound to it

bar(foo(11, 22)); // The reference is bound to the temporary.
                  // The lifetime of the temporary has been extended.