析构函数调用的次数超过了它应该调用的次数。MSVC 中的错误与否?

vla*_*don 18 c++ visual-c++ language-lawyer

这个程序:

#include <iostream>

using namespace std;

struct B {
    B() { cout << "B"; }
    //B(const B& b) { cout << "copyB"; }
    ~B() { cout << "~B"; }
};

struct C : B {

};

void f(B b) {

}

int main() {
    C c;
    f(c);
    return 0;
}

Run Code Online (Sandbox Code Playgroud)

输出B~B~B~B,即三次调用析构函数,为什么?

仅在 MSVC 中。Clang 和 GCC 输出B~B~B(最有可能是正确的)。

有趣的是:如果你取消对 copy-ctor 的注释,它会输出BcopyB~B~B,这是正确的(析构函数调用了两次)。

它是 MSVC 编译器中的错误吗?或者这是正确的行为?

(Visual Studio 2019 最新,cl.exe版本 19.28.29337)

Ayx*_*xan 10

如果打印地址:

#include <stdio.h>

struct B {
    B() {  printf(" B() <%p>\n", (void*)this); }
    ~B() { printf("~B() <%p>\n", (void*)this); }
};

struct C : B { };

void f(B b) { }

int main() {
    C c;
    f(c);
}
Run Code Online (Sandbox Code Playgroud)

输出是:

 B() <000000A013FFFAC4>
~B() <000000A013FFFAA0>
~B() <000000A013FFFBA4>
~B() <000000A013FFFAC4>
Run Code Online (Sandbox Code Playgroud)

如您所见,没有对象被破坏 2 次。似乎有一个临时的参与,据我所知,这是不允许的。这让我相信这是一个错误。这只发生在复制构造函数是微不足道的时候。由于析构函数不是微不足道的,它是可观察的行为,不在 as-if 规则中。


jac*_*k X 3

结论是这是MSVC的一个bug。尽管标准指定在将参数传递给函数时可以创建临时对象,但是,应该遵守许多限制。相关规则写如下:
[class.temporary#3]

当类类型 X 的对象传递给函数或从函数返回时,如果 X 至少有一个合格的复制或移动构造函数([特殊]),则每个此类构造函数都是 trivial,并且X 的析构函数是 trivial 或业已删除,允许实现创建临时对象来保存函数参数或结果对象。临时对象分别从函数参数或返回值构造,并且函数的参数或返回对象被初始化,就像使用合格的普通构造函数来复制临时对象一样(即使该构造函数不可访问或不会被重载选择)执行对象的复制或移动的分辨率)。

在您的示例中,传递的参数的类型为 class C,它派生自基类,B该基类具有非用户提供的复制构造函数和用户提供的析构函数,这将导致派生类C可以具有简单的复制构造函数,但不能具有根据[class.copy.ctor#11.2][class.dtor#8.2]有一个简单的析构函数,它们是以下规则:

如果类 X 的复制/移动构造函数不是用户提供的并且满足以下条件,则它是微不足道的:

  • 选择复制/移动每个直接基类子对象的构造函数是微不足道的

如果析构函数不是用户提供的并且满足以下条件,则它是微不足道的:

  • 该类的所有直接基类都有简单的析构函数

类不会满足所有这些限制C,因此在这种情况下无法创建临时对象。这意味着应该使用复制构造函数从参数初始化参数。这两个具有自动存储期限的对象将分别在块退出时被销毁。也就是说,~B这里只应打印两次。