我对 C++ 相当陌生,当我在构造函数和析构函数的行为中徘徊时,我发现了这个问题:
#include <iostream>
struct Student {
std::string a;
~Student() {
std::cout << "Destructor called\n";
}
} S;
int main() {
std::cout << "Before assigning to S\n";
S = {""};
std::cout << "After assigning to S\n";
}
Run Code Online (Sandbox Code Playgroud)
当我编译上面的代码g++
并运行它时,它会打印:
Before assigning to S
Destructor called
After assigning to S
Destructor called
Run Code Online (Sandbox Code Playgroud)
但是当我更改std::string a;
为 时const char *a;
,它会打印:
Before assigning to S
After assigning to S
Destructor called
Run Code Online (Sandbox Code Playgroud)
谁能解释为什么这一变化使析构函数少运行一次?
pad*_*ddy 16
您正在看到Copy Elision。这是一种优化,在某些情况下将绕过临时对象的构造。
在 GCC 中,可以通过传递以下编译器标志来禁用它(尽管通常不推荐这样做):
-fno-elide-constructors
Run Code Online (Sandbox Code Playgroud)
在您的情况下,我认为优化是由于结构成为微不足道的类型而触发的,因此它被视为 POD(纯旧数据),并且在不构造对象的情况下简单地修改了内存。
显示效果的现场演示-fno-elide-constructors
:https : //godbolt.org/z/c3qrMEr9s
首先,在任何一种情况下,Student
都是聚合。现在,我们检查相关规则S = {""};
一个大括号初始化列表可能会出现在右侧
- 对类类型对象的赋值,在这种情况下,初始化列表作为参数传递给重载决议选择的赋值运算符函数
由于没有适合这种情况的内置候选者,因此唯一可行的候选者是隐式声明的复制赋值运算符,其形式为:
Student&Student::operator=(Student const&)
对应的参数是{""}
。参数传递将启动复制初始化,这意味着参数将从{""}
; 这是一个列表初始化,下面的规则将适用于此
否则,如果 T 是引用类型,则生成一个纯右值。纯右值通过复制列表初始化来初始化其结果对象。然后使用纯右值直接初始化引用。临时的类型是 T 引用的类型,除非 T 是“对 U 的未知边界数组的引用”,在这种情况下,临时的类型是声明 U x[] H 中 x 的类型,其中 H是初始化列表。
这意味着引用将绑定到由 复制初始化的临时对象{""}
。这是一个聚合初始化,因为它Student
是一个聚合。并且临时的生命周期将在 full-expression 处结束,由以下规则决定:
临时对象被销毁作为评估完整表达式 ([intro.execution]) 的最后一步,该表达式(词法上)包含它们的创建点。
因此,正确的输出应该是
Before assigning to S
Destructor called // for the temporary
After assigning to S
Destructor called // for the object S
Run Code Online (Sandbox Code Playgroud)
因此,GCC 在这里是错误的。由于其行为不符合标准规定。相比之下,Clang实现了正确的行为。
归档时间: |
|
查看次数: |
1020 次 |
最近记录: |