为什么struct的析构函数是否运行取决于成员变量的类型?

我对 C++ 相当陌生,当我在构造函数和析构函数的行为中徘徊时,我发现了这个问题:

#include <iostream>

struct Student {
    std::string a;
    ~Student() {
        std::cout << "Destructor calledn";
    }
} S;

int main() {
    std::cout << "Before assigning to Sn";
    S = {""};
    std::cout << "After assigning to Sn";
}

当我编译上面的代码g++并运行它时,它会打印:

Before assigning to S
Destructor called
After assigning to S
Destructor called

但是当我更改std::string a;为 时const char *a;,它会打印:

Before assigning to S
After assigning to S
Destructor called

谁能解释为什么这一变化使析构函数少运行一次?

回答

您正在看到Copy Elision。这是一种优化,在某些情况下将绕过临时对象的构造。

在 GCC 中,可以通过传递以下编译器标志来禁用它(尽管通常不推荐这样做):

-fno-elide-constructors

在您的情况下,我认为优化是由于结构成为微不足道的类型而触发的,因此它被视为 POD(纯旧数据),并且在不构造对象的情况下简单地修改了内存。

显示效果的现场演示-fno-elide-constructors:https : //godbolt.org/z/c3qrMEr9s

  • Is that really copy initialization? Wouldn't that be something like `Student a={2};` whereas the example has `a` already declared as a global and is just `a = {2};` Looks more like an assignment operator where the copy initialization was to a temporary which is bound to a reference for the assignment operator. Supposedly, when bound to a reference it isn't supposed to be elided.
  • Copy elision only applies during initialization, not assignment. This looks like a GCC bug to me.

回答

首先,在任何一种情况下,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

因此,GCC 在这里是错误的。由于其行为不符合标准规定。相比之下,Clang实现了正确的行为。


以上是为什么struct的析构函数是否运行取决于成员变量的类型?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>