在P0593R6('Implicit creation of objects for low-level object manipulation')在C20中被接受后,C23将得到std::start_lifetime_as()
,它“完成了[P0593 R6]中提出的功能”(参见[P0593 R6])。P2590R2、P2679R2和cppreference C++ 23 feature testing页面)。std::start_lifetime_as()
的参考实现是什么样子的?
这样就足够了,还是还有更多?
#include <cstddef>
#include <new>
template<class T>
T* start_lifetime_as(void* p) noexcept
{
new (p) std::byte[sizeof(T)];
return static_cast<T*>(p);
}
字符串
2条答案
按热度按时间x7rlezfr1#
这些函数纯粹是编译器魔法:没有办法在C++中实现这些效果(严格解释),这就是为什么它们被添加到库中。
实现经常“看起来是另一种方式”,或者无法证明发生了未定义的行为,但它们抓住任何给定的示例并“错误编译”它只是时间问题。
qpgpyjmq2#
std::start_lifetime_as
不能完全手工实现,因为它具有不访问存储的特殊属性。我们可以提供的任何实现在理论上都必须访问存储,即使这在实践中可以优化。然而,忽略这个细节,我们可以如下实现它:
C++20 -
std::memmove
实现从C++20开始,
std::memmove
和std::memcpy
是“魔术”,因为它们隐式地开始了目标对象的生存期。std::memmove
可以有相同的源和目的地,所以我们可以劫持它的神奇属性并轻松实现std::start_lifetime_as
:字符串
之所以这样做是因为:
这两个函数[
std::memcpy
和std::memmove
]都在将字符序列复制到目标之前,在目标存储区域中隐式地创建对象。std::memmove
将内存区域[p, p + sizeof(T))
转换为隐式创建对象的区域。你可能会问这个对象的类型是什么:对于被指定为隐式创建对象的每个操作,如果这样做会导致程序具有定义的行为,则该操作在其指定的存储区域中隐式创建并启动零个或多个 * 隐式生存期类型 * 的对象的生存期。
如果没有
std::launder
,则可能会在此内存区域中创建T
以外类型的对象。然而,std::launder
有一个前提条件,即在p
处必须有一个T
(参见[ptr.launder] p2),因此编译器必须在那里创建T
以满足前一段。C++17 -在放置方面的实现新增
即使
std::memmove
没有“魔法”,std::start_lifetime_as
也是可以实现的,P0593 R6解释了如何实现。该论文中的解释早于std::memmove
被赋予神奇的属性,这就是为什么它建议一个更复杂的实现:如果目标类型是一个可平凡复制的隐式生存期类型,这可以通过将存储复制到其他地方来实现,使用类似字节类型的数组的placement new,并将存储复制回其原始位置,然后使用
std::launder
获取指向新创建的对象的指针。型
std::byte[]
执行placement new时,内存变得不确定,因此不会保留对象表示。*volatile
类型的生存期。