构造函数或赋值运算符

Constructor or Assignment Operator

提问人:Julian Popov 提问时间:5/17/2010 最后编辑:Julian Popov 更新时间:11/8/2023 访问量:5678

问:

你能帮我吗 C++ 标准中有定义描述在这种情况下哪一个将被称为构造函数或赋值运算符:

#include <iostream>

using namespace std;

class CTest
{
public:

 CTest() : m_nTest(0)
 {
  cout << "Default constructor" << endl;
 }

 CTest(int a) : m_nTest(a)
 {
  cout << "Int constructor" << endl;
 }

 CTest(const CTest& obj)
 {
  m_nTest = obj.m_nTest;
  cout << "Copy constructor" << endl;
 }

 CTest& operator=(int rhs)
 {
  m_nTest = rhs;
  cout << "Assignment" << endl;
  return *this;
 }

protected:
 int m_nTest;
};

int _tmain(int argc, _TCHAR* argv[])
{
 CTest b = 5;

 return 0;
}

还是只是编译器优化的问题?

C++ 复制构造函数 赋值运算符

评论

0赞 Julian Popov 5/17/2010
根据编译器的不同,“CTest b(5)”和“CTest b = 5”之间会有什么区别吗?
1赞 eemz 5/17/2010
否,第一种语法是用于创建类实例并向构造函数提供参数的常规 C++ 语法。第二种语法只是一个替代措辞,如果构造函数只接受一个参数,则可以使用该措辞。它的存在使 C 代码无需修改即可编译。
0赞 Julian Popov 5/17/2010
@joefis为什么你给你答案只是评论?你能证明你说的话吗?

答:

13赞 Konrad Rudolph 5/17/2010 #1

在这种情况下,它始终是构造函数采用调用的构造函数。这称为隐式转换,在语义上等同于以下代码:int

CTest b(5);

赋值运算符永远不会在初始化中调用。请考虑以下情况:

CTest b = CTest(5);

在这里,我们显式调用构造函数(取一个),然后使用该临时值来初始化 .但同样,从未调用过赋值运算符。intb

严格来说,在 C++17 之前,这两种情况都可以在创建 类型的对象后调用复制(或移动)构造函数。但事实上,C++ 标准积极鼓励编译器优化复制构造函数调用这里1,并且所有编译器都优化了几十年。CTest

从 C++17 开始,上面的代码不再调用复制构造函数:即使代码看起来仍然像是执行了复制(或移动),值 的值直接构造到 的存储中,不会创建临时的。阿拉伯数字CTest(5)b


1 参见 ISO/IEC 14882:1998 [class.copy]/15。这被称为复制 elison(特别是 URVO,它令人困惑地代表“未命名的返回值优化”,尽管它也适用于不是返回语句的初始化表达式)。

2 参见 ISO/IEC 14882:2017 [dcl.init]/17.6.1(工作草案 N4659)。

评论

5赞 David Rodríguez - dribeas 5/17/2010
+1,但我会尽量避免在“默认构造函数取 int”这句话中出现“default”。我认为只说“int 构造函数”或“取 int 的构造函数”会更清楚。默认值在标准中具有特定的含义(无参数)。
0赞 BJovke 11/26/2016
如果构造函数和赋值运算符方法是不同的代码,则调用赋值运算符。
1赞 Konrad Rudolph 11/26/2016
@BJovke 否 — 从不在初始化中。
0赞 Lanchon 8/8/2023
这个答案是不合适的,因为它没有来源。像 §12.8/15 这样的引用仅在未指定的 C++ 标准规范的特定版本的上下文中才有意义,并且其中有太多,因此没有提供实际的引用。此外,我认为这个答案是错误的,直到看到相反的证据,因为如果没有提供来源,任何人都可以将自己的观点陈述为事实。(续)
0赞 Lanchon 8/8/2023
(续)从字面上看是:对 B 调用构造函数 (),对 Temp 调用构造函数 (5),从 Temp 调用 B 的赋值,并销毁 Temp。 通常,不允许 C++ 编译器通过假设构造函数和运算符的属性来进行“自上而下”的优化。例如:如果 A 和 B 是 int,则可以优化,但对于 A 和 B 可能是用户定义类型的一般情况,则无法这样做。相比之下,编译器应按编写的方式编译代码,然后积极优化生成的中级/低级代码。(续)CTest b = CTest(5);a + b - ba
8赞 anon 5/17/2010 #2

这里发生的事情有点取决于你的编译器。它可以使用 int 构造函数创建一个临时对象,然后从该临时对象复制构造 b。但是,它很可能会省略复制构造函数调用。在这两种情况下,都不会使用赋值运算符。

评论

1赞 Julian Popov 5/17/2010
根据编译器的不同,“CTest b(5)”和“CTest b = 5”之间会有什么区别吗?
0赞 5/17/2010
@ju 是的,我们就是这么说的。第一个不会使用复制构造函数,第二个可能会。
1赞 5/17/2010
@ju 底线 - 因为C++标准说它可以 - 恐怕你的理论是错误的。复制构造函数(这就是 Ctest b = 3 所暗示的)需要一个 CTest 类型的对象,但你给它一个 int,所以编译器可以(如果它愿意)使用另一个构造函数构造一个临时构造函数,而一些较旧的编译器将做到这一点。
1赞 Axel 5/17/2010
AFAIK,康拉德给出的答案是正确的。构造一个临时的编译器,然后在此处调用复制构造函数不符合标准。
1赞 Matthieu M. 5/17/2010
@Axel:为了扩展一点,Konrad 引用了标准鼓励编译器进行优化。“鼓励”不同于“授权”。
5赞 visitor 5/17/2010 #3

CTest b = 5;是完全等价的 涉及两个构造函数:一个采用一个(隐式从整数 5 转换),另一个采用复制构造函数。此处不涉及赋值运算符。CTest b(CTest(5));int

编译器可能会优化不必要的副本,因此结果就像您键入了 .因此,在运行时,看到打印的“复制构造函数”(带有选项的 GCC)或不打印(默认为 GCC)都是程序的有效输出。CTest b(5)-fno-elide-constructors

但是,从概念上讲,编译器需要检查是否存在可访问且合适的复制构造函数。如果 a) 复制构造函数是私有的/受保护的(不可访问)或 b) 复制构造函数通过非常量引用获取参数(不能接受临时来自 - VC++ 可能会接受它作为非标准编译器扩展,则表单将无法编译。CTest b = 5;CTest(5)

士气是:没有简单的方法可以通过查看代码来判断复制构造函数在程序中被调用的位置和次数。复制通常可以省略,因此永远不应该依赖复制构造函数的副作用。如果它做了它应该做的事情,那么编译器是否消除了一些不必要的复制构造函数调用对你来说应该无关紧要。