如果类 S 是一个聚合或具有至少一个平凡的合格构造函数和一个平凡的、未删除的析构函数,则它是一个隐式生命周期类。
并且可以使用 malloc 创建隐式生命周期对象。见这个例子。
#include <cstdlib>
struct X { int a, b; };
X* MakeX()
{
// One of possible defined behaviors:
// the call to std::malloc implicitly creates an object of type X
// and its subobjects a and b, and returns a pointer to that X object
X* p = static_cast<X*>(std::malloc(sizeof(X)));
p->a = 1;
p->b = 2;
return p;
}
但是
struct A {std::string s;};
也是一个聚合。但这会像我预期的那样产生一个异常,因为对 s 的赋值会首先破坏 s 而 s 是无效的,因为它从未被构造过。
#include <cstdlib>
#include <string>
#include <iostream>
struct X { std::string s; };
X* MakeX()
{
X* p = static_cast<X*>(std::malloc(sizeof(X)));
p->s = "abc";
return p;
}
int main()
{
static_assert(std::is_aggregate_v<X>);
auto x = MakeX();
std::cout << x->s << "\n";
}
那么为什么聚合被认为是隐式生命周期类型?
是的,作为聚合
X
是隐式生命周期类型(参见 [class.prop]/9),但重要的是它的成员 s
是 not 隐式生命周期类型(参见相同的参考资料,例如因为 std::string
有一个非平凡的析构函数)。
结果是
std::malloc(sizeof(X))
将隐式创建一个 X
对象并开始其生命周期,但它 not 开始该 s
对象的 X
子对象的生命周期。这遵循 [intro.object]/10 指定它开始隐式生命周期对象的生命周期,但没有指定任何关于非隐式生命周期(子)对象的信息。标准中也没有其他内容可以指定开始这些子对象的生命周期,例如构造函数调用和聚合初始化的规则会。
因此
p->s = "abc";
仍然会导致在其生命周期之外访问对象的未定义行为。
您必须在
s
之后首先显式地开始 malloc
子对象的生命周期,例如与:
std::construct_at(&p->s);
在那之后,你可以做
p->s = "abc";
.