为啥这里需要 std::launder ?
Posted
技术标签:
【中文标题】为啥这里需要 std::launder ?【英文标题】:Why is std::launder required here?为什么这里需要 std::launder ? 【发布时间】:2021-10-22 10:26:08 【问题描述】:我正在阅读 cppreference 并且在 std::aligned_storage 的示例中有这个向量/数组类的示例:
template<class T, std::size_t N>
class static_vector
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
// IF you want to see possible implementation of aligned storage see link.
// It's very simple and small, it's just a buffer
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
if( m_size >= N ) // possible error handling
throw std::bad_alloc;
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
// note: needs std::launder as of C++17
// WHY
return *reinterpret_cast<const T*>(&data[pos]);
// Delete objects from aligned storage
~static_vector()
for(std::size_t pos = 0; pos < m_size; ++pos)
// note: needs std::launder as of C++17
// WHY?
reinterpret_cast<T*>(&data[pos])->~T();
;
基本上每个元素所在的每个存储桶/区域/内存地址都是一个字符缓冲区。在存在的每个元素位置,都会在 T 类型的缓冲区中完成新的放置。因此,当返回该缓冲区的位置时,可以将 char 缓冲区强制转换为 T*,因为在那里构造了一个 T。为什么从 C++17 开始需要 std::launder?我已经问了很多关于这个的问题,似乎同意:
alignas(alignof(float)) char buffer [sizeof(float)];
new (buffer) float;// Construct a float at memory, float's lifetime begins
float* pToFloat = buffer; // This is not a strict aliasing violation
*pToFloat = 7; // Float is live here
这显然不是严格的别名违规,完全没问题。那么为什么这里需要 std::launder 呢?
【问题讨论】:
您必须使用new
返回的指针来访问创建的对象。不允许投射 buffer
。 std::launder
可用于从 buffer
获取有效指针。
附带说明,该评论有些误导。需要使用 std::launder 才能使代码有效。在 c++17 之前缺少所需的工具,使得这样的代码无法合法编写。
是的。 char buffer[32]; MyStruct * ptr = new (&buffer[0]) MyStruct; ptr->~MyStruct();
很好(假设满足对齐和大小要求)。
Zebrafish> 是的,添加 std::launder 正是为了有一种合法的方式来编写它,而不是依赖未定义的行为并希望编译器会做正确的事情。
这正是我在上面的第二条评论中提到的:该注释具有误导性。在 C++17 之前,没有合法的方式来编写该代码。
【参考方案1】:
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
为什么从 C++17 开始需要 std::launder?
您可以从reinterpret_cast
取消引用指针的条件很少。这不是其中的一个。您正在将指向一种类型的指针转换为指向完全不相关类型的指针,并通读它。见here。
std::launder
允许您派生一个有效的指针。它存在的全部理由就是给你这个。
在 C++17 之前,您必须保存从位置 new 返回的指针,因为这是唯一有效的指向 T
的指针,而在 std::launder
之前,您无法从缓冲区中创建一个.
我已经对此提出了很多问题,并且似乎同意...这显然不是严格的混叠违规,完全没问题。那么为什么这里需要 std::launder 呢?
这不好,出于同样的原因。也没有同意这很好。请参阅@eerorika 在your question here 上的回答。
【讨论】:
如果在动态分配的内存中访问和写入对象的唯一方法是使用placement new,那么这意味着任何将malloc用于int缓冲区而不使用placement new的代码都是不明确的。而且在 C++ 放置 new 之前,不能使用 malloc 作为缓冲区或 int 或任何其他类型吗? @Zebrafish 新位置从 C++ 的第一个完整版本就出现了。在此之前不是这样,但那时您也必须在构造函数中手动分配this
。
另外,implicit-lifetime types 有一个special rule,它允许编译器隐式启动满足特定条件的对象的生命周期。标量类型满足这些条件,因此简单地将 malloc 的结果转换为标量数组是明确定义的。不过,只有第一次,不要存储 void* 并再次施放。
@spectras “隐式生命周期类型”。从何时起?您是在谈论新提案吗? open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0593r5.html ?您是否在该链接中看到,大约一两页,它 malloc 并分配给 struct X 指针,然后访问其成员。它说虽然这是惯用的,但它在 C++ 中是未定义的。但是,从 cppreference 页面来看,struct X 应该是“聚合类型”,即“隐式生命周期”类型。你能澄清一下吗?
@Zebrafish 是的,它是最近添加的,可能与 C++20 一样新——我不知道它是否源于这个特定的 PR。在此之前,您需要将placement-new 放入malloc 创建的存储中。以上是关于为啥这里需要 std::launder ?的主要内容,如果未能解决你的问题,请参考以下文章