我在 C++ 行为中发现了一个令我困惑的有趣案例。预先警告,我不会在自己的代码中执行此操作。这只是为了了解标准。
这里我有一个没有显式默认构造函数的类 M。它继承自具有显式默认构造函数的类 Base。当我像 M() 或 M{} 那样构造 M 类时,它将把成员 r 归零。如果我在没有任何括号的情况下构造它,则不会初始化成员 r,但会调用基类构造函数(如预期)。对我来说奇怪的是,这种行为似乎 M 类现在有两个隐式默认构造函数 - 一个使成员 r 未初始化,但调用所有继承的构造函数,另一个将其清零。
如果 M 是纯 POD,那么我理解这种行为(默认初始化与统一化),但这里甚至添加 M() = default; 仍然会导致它与 new M() 和 new M 的行为不同。
因为两个编译器执行相同的操作,所以看起来不像是错误,但是这个功能是什么?这有点令人困惑。有没有一个简化的方案来理解它。
这是代码的 godbolt 链接https://godbolt.org/z/9srh48Y5Y 我在这里做一些技巧来强制将类分配在一些垃圾填充的内存上,以便在 godbolt 上看到该行为。
#include <iostream>
#include <cstring>
//Base class with default constructor
//explicitly defined
class Base
{
public:
int k;
Base() : k(0xF){}
};
//A class with default constructor
//explicitly defined
class C
{
public:
int z;
C() : z(13){}
};
//Class M does not have explicitly defined
//default constructor
class M : public Base
{
public:
int r;
C c;
//M() = default;
//M() {}
};
union Buffer
{
char bbb[sizeof(M)];
double ddd;
long long llll;
};
int main()
{
//to demonstrate which members are initialized
//I need to fill the memory with some garbage
//before using it to construct a class
Buffer buf1;
Buffer buf2;
Buffer buf3;
std::memset(buf1.bbb, 0xCD, sizeof(Buffer));
std::memset(buf2.bbb, 0xCD, sizeof(Buffer));
std::memset(buf3.bbb, 0xCD, sizeof(Buffer));
M* m1 = new (buf1.bbb) M{};
M* m2 = new (buf2.bbb) M();
M* m3 = new (buf3.bbb) M;
std::cout << m1->k << " " << m1->c.z << " " << m1->r << std::endl;
std::cout << m2->k << " " << m2->c.z << " " << m2->r << std::endl;
std::cout << m3->k << " " << m3->c.z << " " << m3->r << std::endl;
}i
Run Code Online (Sandbox Code Playgroud)
clang 16 (x86-64) 的输出:
15 13 0
15 13 0
15 13 -842150451
Run Code Online (Sandbox Code Playgroud)
海湾合作委员会也这样做。
编辑
这里是评论中讨论的第二个更有趣的代码变体(https://godbolt.org/z/aso8Gz4sh)。
#include <iostream>
#include <cstring>
//Base class with default constructor
//explicitly defined
class Base
{
public:
int k;
Base() : k(0xF){}
};
//A class with default constructor
//explicitly defined
class C
{
public:
int z1;
int z2;
C() : z1(13) {}
};
//Class M does not have explicitly defined
//default constructor
class M : public Base
{
public:
int r;
C c;
//M() = default;
//M() {}
};
union Buffer
{
char bbb[sizeof(M)];
double ddd;
long long llll;
};
int main()
{
//to demonstrate which members are initialized
//I need to fill the memory with some garbage
//before using it to construct a class
Buffer buf1;
Buffer buf2;
Buffer buf3;
std::memset(buf1.bbb, 0xCD, sizeof(Buffer));
std::memset(buf2.bbb, 0xCD, sizeof(Buffer));
std::memset(buf3.bbb, 0xCD, sizeof(Buffer));
M* m1 = new (buf1.bbb) M{};
M* m2 = new (buf2.bbb) M();
M* m3 = new (buf3.bbb) M;
std::cout << m1->k << " " << m1->c.z1 << " " << m1->c.z2 << " " << m1->r << std::endl;
std::cout << m2->k << " " << m2->c.z1 << " " << m2->c.z2 << " " << m2->r << std::endl;
std::cout << m3->k << " " << m3->c.z1 << " " << m3->c.z2 << " " << m3->r << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
铿锵 16.0 输出
15 13 -842150451 0
15 13 0 0
15 13 -842150451 -842150451
Run Code Online (Sandbox Code Playgroud)
海湾合作委员会 13.2 输出
15 13 0 0
15 13 0 0
15 13 -842150451 -842150451
Run Code Online (Sandbox Code Playgroud)
这是预期的行为。 M是一个聚合,因为它没有:
virtual,private或protected基类private或protected数据成员virtual成员函数这意味着它遵循聚合初始化规则。
聚合初始化的规则基本上如下:
M不带任何括号的 时会发生的情况。M这就是当您构造带有空括号的 时会发生的情况M{}M这就是当您构造using 空括号时会发生的情况M()。所以:
new M:所有子对象都是默认初始化的(对于POD对象来说是未初始化的)new M{}或者new M():所有子对象都进行值初始化(这意味着 POD 对象的零初始化)new M{{}, 10}:M对象的Base子对象使用 初始化{},其r子对象使用 初始化10,其子c对象使用值初始化。