为什么表达式 'T foo = T()' 不是拷贝初始化?
在这里,我们将从临时对象`A(5)`对变量`a`进行了拷贝初始化。根据C++标准12.2/2,实现允许在这里跳过调用拷贝构造函数。
然而,根据我个人的理解,我不认为这是正确的。12.2.2中的示例涉及在构造本地对象之前将临时对象传递给函数。
那么,为什么在这种情况下不会调用拷贝构造函数呢?这是因为根据C++标准12.2/2,当进行拷贝初始化时,编译器有权选择跳过调用拷贝构造函数的步骤。这意味着编译器可以将临时对象直接赋值给变量,而不需要调用拷贝构造函数。
那么,如何解决这个问题呢?为了确保调用拷贝构造函数,我们可以改为使用直接初始化来构造变量`a`。直接初始化使用括号来构造对象,而不是使用`=`进行赋值。这样,我们可以确保在构造变量`a`时调用拷贝构造函数。
下面是使用直接初始化的示例代码:
A a(5);
通过使用直接初始化,我们可以确保在构造变量`a`时调用拷贝构造函数,从而解决了原始问题。
总结起来,这个问题的出现是因为根据C++标准12.2/2,编译器在进行拷贝初始化时有权选择跳过调用拷贝构造函数的步骤。为了解决这个问题,我们可以改为使用直接初始化来构造变量,并确保在构造变量时调用拷贝构造函数。
为什么表达式 'T foo = T()' 不是使用 copy 初始化的原因以及解决方法:
在C++11标准中,表达式 'T foo = T()' 称为变量 foo 的 copy 初始化。标准中规定,所选中的函数使用初始化表达式作为参数调用;如果该函数是一个构造函数,调用将初始化一个目标类型的 cv 去除版本的临时对象。该临时对象是一个 prvalue。调用的结果(对于构造函数情况下的临时对象)然后被用于根据上述规则直接初始化作为 copy 初始化目标的对象。在某些情况下,实现可以通过将中间结果直接构造到正在初始化的对象中来消除该直接初始化中固有的复制,参见12.2、12.8。
这意味着编译器查找适当的构造函数来处理 'T(5)',创建一个临时对象,并将该临时对象复制到 foo 中。但在什么情况下可以省略复制呢?
让我们看看12.8/31所说的内容:
当满足某些条件时,即使对象的复制/移动构造函数和/或析构函数具有副作用,实现也允许省略类对象的复制/移动构造。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且在没有优化的情况下,该对象的销毁发生在两个对象中的任何一个对象将被销毁的较晚时间。这种复制/移动操作的省略称为复制省略,可以在以下情况下允许(可以组合以消除多个复制):
- 当一个未绑定到引用的临时类对象将被复制/移动到具有相同 cv 去除类型的类对象时,可以通过直接构造临时对象到省略的复制/移动的目标中来省略复制/移动操作。
有了这些信息,我们来看一下表达式 'T foo = T()' 的情况:
1. 编译器看到一个带有 copy 初始化的声明。
2. 选择 'T(int)' 构造函数来初始化一个临时对象。
3. 因为临时对象没有绑定到引用,并且它与 copy 初始化表达式中的目标类型 'T' 具有相同的类型,编译器被允许直接构造一个对象到 foo 中,省略临时对象。
问题的出现原因是因为使用表达式 "T foo = T()" 进行拷贝初始化时,编译器会自动优化掉内部的拷贝构造函数调用,导致内部的对象不会被复制。解决方法是通过强制编译器进行默认构造函数的调用,然后再进行赋值操作。
具体来说,这种现象是由于C++标准规定了拷贝初始化的语法。标准中明确规定,T = x;
语句可以等价于 T(x);
。当我们使用 T(T(x))
这种形式时,显然内部的 T
是多余的,所以编译器会自动删除它。
为了获得期望的行为,我们可以强制编译器进行默认构造函数的调用,然后再进行赋值操作。具体做法是先声明一个 A
类型的对象 a;
,这样它会被完全构造,之后就无法再次调用构造函数。然后我们可以使用 a = A(5);
将值为 5 的 A
对象赋值给 a
。
需要注意的是,这种现象在不同的编译器中可能会有不同的行为。C++标准允许编译器进行不同的优化操作。因此,我们不能依赖这种语法的行为,需要根据具体情况来选择合适的解决方法。