为什么 std::aligned_storage 在 C++23 中被弃用以及使用什么替代?

bit*_*ask 62 c++ memory-alignment deprecated c++23

我刚刚看到 C++ 23计划弃用std::aligned_storageandstd::aligned_storage_t以及std::aligned_unionstd::aligned_union_t

据我所知,在对齐存储中放置新对象并不是特别constexpr友好,但这似乎不是完全丢弃该类型的好理由。这让我假设使用std::aligned_storage和朋友还存在一些我不知道的其他基本问题。那会是什么?

是否有建议替代这些类型?

Ted*_*gmo 64

以下是三段摘录P1413R3

背景

aligned_*对代码库有害,不应使用。高层次上:

  • 使用aligned_*调用未定义的行为(类型无法提供存储。)
  • 保证不正确(标准仅要求类型至少与请求的大小一样大,但没有对大小设置上限。)
  • 由于多种原因,API 是错误的(请参阅“关于 API”。)
  • 由于 API 是错误的,几乎所有用法都涉及相同的重复预工作(请参阅“现有用法”。)

关于 API

std::aligned_*遭受许多糟糕的 API 设计决策的困扰。其中一些是共享的,一些是特定的。至于分享的内容,主要存在三个问题(为了简洁,这里仅列出一个)

  • reinterpret_cast需要使用才能访问该值

没有实例.data(),甚至有.data实例std::aligned_*。相反,API 要求您获取对象的地址,使用reinterpret_cast<T*>(...)它进行调用,然后最终间接得到结果指针,为您提供一个T&. 这不仅意味着它不能在 中使用constexpr,而且在运行时更容易意外调用未定义的行为。reinterpret_cast要求使用 API 是不可接受的。


建议更换

最简单的替代aligned_*实际上不是库功能。相反,用户应该使用正确对齐的 数组std::byte,可能需要调用std::max(std::initializer_list<T>). <cstddef>这些可以分别在和标题中找到 <algorithm>(本节末尾有示例)。不幸的是,这种替代并不理想。要访问 的值aligned_*,用户必须调用reinterpret_cast该地址来读取字节作为T实例。使用字节数组作为替代并不能避免这个问题。也就是说,重要的是要认识到,继续使用reinterpret_cast它已经存在的地方并不比在以前不存在的地方新引入它那么糟糕。...

上述已接受的停用提案的部分aligned_*后面是一些示例,例如这两个替代建议:

// To replace std::aligned_storage
template <typename T>
class MyContainer {
private:
    //std::aligned_storage_t<sizeof(T), alignof(T)> t_buff;
    alignas(T) std::byte t_buff[sizeof(T)];
};
Run Code Online (Sandbox Code Playgroud)
// To replace std::aligned_union
template <typename... Ts>
class MyContainer {
private:
    //std::aligned_union_t<0, Ts...> t_buff;
    alignas(Ts...) std::byte t_buff[std::max({sizeof(Ts)...})];
};
Run Code Online (Sandbox Code Playgroud)

  • 我们真正需要的是一种在不构造其元素的情况下声明“正确类型化”数组的新方法,例如:“[[uninitialized_storage]] T t_arr[len];”,其中编译器将分配足够大小和对齐的数组,只是不调用任何构造函数。允许开发人员根据需要使用“placement-new”来初始化每个“T”元素。我知道,一厢情愿…… (7认同)
  • 另外,“reinterpret_cast”是使用此习惯用法/类型的必要部分的论点是错误的,因为placement-new将返回正确的“T*”。因此,一个不需要重新解释任何点的用例是可以想象和合理的。不管怎样,现在它已经消失了,人们将不得不自己推出。 (5认同)
  • 天哪,我讨厌 C++ 标准。他们让一切变得如此复杂,而忘记了基础知识。 (4认同)
  • @TedLyngmo如果我理解[Barry的这个答案](/sf/answers/5024122771/)上的脚注,C++23应该能够做到这一点——但我没有遵循c ++23 非常接近。另一种肯定可行的方法是,当“T”不是时,在联合体中抛出一个空的普通结构体以成为活动成员(例如“union { structempty_type {}empty; Tentry; }t_arr[len]”。然后“ std::construct_at` 合法地更改活动成员,并且仍然可以在 constexpr 上下文中访问 `entry`,而不需要 `reinterpret_cast`。 (3认同)
  • @digito_evo 我_认为_我遵循了规则,我使用了“alignas(T) std::byte buf[sizeof(T)];”作为建议的 P1413R3 对于_一个_元素,并且只将其增大了“N”倍。这需要一位语言律师才能肯定:-) (2认同)