你能帮我看看 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;
}
或者这只是编译器优化的问题?
在这种情况下,总是调用带有
int
的构造函数。这称为隐式转换,在语义上等同于以下代码:
CTest b(5);
赋值运算符从不在初始化中被调用。考虑以下情况:
CTest b = CTest(5);
在这里,我们显式调用构造函数(采用
int
),然后使用该临时值来初始化 b
。但再一次,没有赋值运算符被永远调用。
严格来说,C++17 之前的两种情况都可以在创建
CTest
类型的对象后调用复制(或移动)构造函数。但事实上,C++ 标准积极鼓励编译器优化此处的复制构造函数调用1,并且所有编译器都对此进行了数十年的优化。
自 C++17 起,上面的代码不再调用复制构造函数:即使代码看起来仍然像是执行了复制(或移动),
CTest(5)
的值会直接构造到存储中b
,没有创建临时文件。2
1 请参阅 ISO/IEC 14882:1998 [class.copy]/15。这被称为 copy elison(特别是 URVO,它令人困惑地代表“未命名返回值优化”,尽管它也适用于不是 return 语句的初始化表达式)。
2 请参阅 ISO/IEC 14882:2017 [dcl.init]/17.6.1(工作草案 N4659)。
这里发生的情况在一定程度上取决于您的编译器。它可以使用 int 构造函数创建一个临时对象,然后从该临时对象复制构造 b。然而,它很可能会省略复制构造函数调用。在这两种情况下都不会使用赋值运算符。
CTest b = 5;
与 CTest b(CTest(5));
完全相同,涉及两个构造函数:一个采用 int
(从整数 5 隐式转换),另一个是复制构造函数。赋值运算符与这里无关。
编译器很可能会优化掉不必要的副本,因此结果就像您输入了
CTest b(5)
一样。因此,在运行时,无论看到“复制构造函数”打印(带有 -fno-elide-constructors
选项的 GCC)还是不打印(GCC 默认情况下)都将是程序的有效输出。
但是,从概念上讲,编译器需要检查是否存在可访问且合适的复制构造函数。如果 a) 复制构造函数是私有/受保护的(不可访问)或 b) 复制构造函数通过非常量引用获取参数(不能接受来自
CTest b = 5;
的临时参数,则 CTest(5)
形式将无法编译 - VC++不过,可以接受它作为非标准编译器扩展)。
士气是:没有简单的方法可以通过查看代码来判断程序中复制构造函数被调用的位置和次数。复制通常可以被省略,因此您永远不应该依赖复制构造函数的副作用。如果它做了它应该做的事情,那么编译器是否消除了一些不必要的复制构造函数调用对您来说并不重要。