什么时候可以安全地重复使用来自普通可破坏对象的内存而不洗
When is it safe to re-use memory from a trivially destructible object without laundering
关于以下代码:
class One {
public:
double number{};
};
class Two {
public:
int integer{};
}
class Mixture {
public:
double& foo() {
new (&storage) One{1.0};
return reinterpret_cast<One*>(&storage)->number;
}
int& bar() {
new (&storage) Two{2};
return reinterpret_cast<Two*>(&storage)->integer;
}
std::aligned_storage_t<8> storage;
};
int main() {
auto mixture = Mixture{};
cout << mixture.foo() << endl;
cout << mixture.bar() << endl;
}
我没有为类型调用析构函数,因为它们很容易被破坏。我对该标准的理解是,为了安全起见,我们需要在将存储指针传递给 reinterpret_cast
之前清洗存储指针。然而,std::optional 在 libstdc++ 中的实现似乎并没有使用 std::launder()
,而是简单地将对象构造到联合存储中。 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional。
我上面的例子是明确定义的行为吗?我需要做什么才能让它发挥作用?工会能让这项工作成功吗?
在您的代码中,您确实需要 std::launder
才能让您的 reinterpret_cast
执行您希望它执行的操作。这是与重用内存不同的问题。根据标准([expr.reinterpret].cast]7),你的表达式
reinterpret_cast<One*>(&storage)
相当于:
static_cast<One*>(static_cast<void*>(&storage))
但是,外部 static_cast
没有成功生成指向新创建的 One
对象的指针,因为根据 [expr.static.cast]/13,
if the original pointer value points to an object a, and there is an object b of type T
(ignoring cv-qualification) that is pointer-interconvertible (6.9.2)
with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.
也就是说,结果指针仍然指向 storage
对象,而不是指向嵌套在其中的 One
对象,并且将它用作指向 One
对象的指针会违反严格的别名规则。您必须使用 std::launder
强制生成的指针指向 One
对象。或者,正如评论中指出的那样,您可以直接使用 placement new 返回的指针,而不是从 reinterpret_cast
.
获得的指针
如果按照评论中的建议,您使用联合而不是 aligned_storage
,
union {
One one;
Two two;
};
你会回避指针相互转换的问题,所以 std::launder
将不需要考虑到非指针相互转换。但是,仍然存在重新使用内存的问题。在这种特殊情况下,由于您的 One
和 Two
类 不包含 [=29] 的任何非静态数据成员,因此不需要 std::launder
来重用=]-限定或引用类型 ([basic.life]/8).
最后,有一个问题,为什么 libstdc++ 的 std::optional
实现不使用 std::launder
,即使 std::optional
可能包含包含非静态数据的 类 const
限定或引用类型的成员。正如评论中指出的那样,libstdc++ 是实现的一部分,当实现者知道 GCC 仍然可以在没有它的情况下正确编译代码时,可以简单地省略 std::launder
。导致引入 std::launder
(参见 CWG 1776 and the linked thread, N4303, P0137)的讨论似乎表明,在比我更了解标准的人看来,确实需要 std::launder
为了使 std::optional
的基于联合的实现在 const
限定或引用类型的成员存在的情况下得到明确定义。但是,我不确定标准文本是否足够清晰,足以让这一点显而易见,并且可能值得讨论如何澄清它。
关于以下代码:
class One {
public:
double number{};
};
class Two {
public:
int integer{};
}
class Mixture {
public:
double& foo() {
new (&storage) One{1.0};
return reinterpret_cast<One*>(&storage)->number;
}
int& bar() {
new (&storage) Two{2};
return reinterpret_cast<Two*>(&storage)->integer;
}
std::aligned_storage_t<8> storage;
};
int main() {
auto mixture = Mixture{};
cout << mixture.foo() << endl;
cout << mixture.bar() << endl;
}
我没有为类型调用析构函数,因为它们很容易被破坏。我对该标准的理解是,为了安全起见,我们需要在将存储指针传递给 reinterpret_cast
之前清洗存储指针。然而,std::optional 在 libstdc++ 中的实现似乎并没有使用 std::launder()
,而是简单地将对象构造到联合存储中。 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional。
我上面的例子是明确定义的行为吗?我需要做什么才能让它发挥作用?工会能让这项工作成功吗?
在您的代码中,您确实需要 std::launder
才能让您的 reinterpret_cast
执行您希望它执行的操作。这是与重用内存不同的问题。根据标准([expr.reinterpret].cast]7),你的表达式
reinterpret_cast<One*>(&storage)
相当于:
static_cast<One*>(static_cast<void*>(&storage))
但是,外部 static_cast
没有成功生成指向新创建的 One
对象的指针,因为根据 [expr.static.cast]/13,
if the original pointer value points to an object a, and there is an object b of type
T
(ignoring cv-qualification) that is pointer-interconvertible (6.9.2) with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.
也就是说,结果指针仍然指向 storage
对象,而不是指向嵌套在其中的 One
对象,并且将它用作指向 One
对象的指针会违反严格的别名规则。您必须使用 std::launder
强制生成的指针指向 One
对象。或者,正如评论中指出的那样,您可以直接使用 placement new 返回的指针,而不是从 reinterpret_cast
.
如果按照评论中的建议,您使用联合而不是 aligned_storage
,
union {
One one;
Two two;
};
你会回避指针相互转换的问题,所以 std::launder
将不需要考虑到非指针相互转换。但是,仍然存在重新使用内存的问题。在这种特殊情况下,由于您的 One
和 Two
类 不包含 [=29] 的任何非静态数据成员,因此不需要 std::launder
来重用=]-限定或引用类型 ([basic.life]/8).
最后,有一个问题,为什么 libstdc++ 的 std::optional
实现不使用 std::launder
,即使 std::optional
可能包含包含非静态数据的 类 const
限定或引用类型的成员。正如评论中指出的那样,libstdc++ 是实现的一部分,当实现者知道 GCC 仍然可以在没有它的情况下正确编译代码时,可以简单地省略 std::launder
。导致引入 std::launder
(参见 CWG 1776 and the linked thread, N4303, P0137)的讨论似乎表明,在比我更了解标准的人看来,确实需要 std::launder
为了使 std::optional
的基于联合的实现在 const
限定或引用类型的成员存在的情况下得到明确定义。但是,我不确定标准文本是否足够清晰,足以让这一点显而易见,并且可能值得讨论如何澄清它。