c++ std::start_lifetime_as()

cedebl8k  于 2023-08-09  发布在  其他
关注(0)|答案(2)|浏览(329)

P0593R6('Implicit creation of objects for low-level object manipulation')在C20中被接受后,C23将得到std::start_lifetime_as(),它“完成了[P0593 R6]中提出的功能”(参见[P0593 R6])。P2590R2P2679R2cppreference 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);
}

字符串

x7rlezfr

x7rlezfr1#

这些函数纯粹是编译器魔法:没有办法在C++中实现这些效果(严格解释),这就是为什么它们被添加到库中。
实现经常“看起来是另一种方式”,或者无法证明发生了未定义的行为,但它们抓住任何给定的示例并“错误编译”它只是时间问题。

qpgpyjmq

qpgpyjmq2#

std::start_lifetime_as不能完全手工实现,因为它具有不访问存储的特殊属性。我们可以提供的任何实现在理论上都必须访问存储,即使这在实践中可以优化。
然而,忽略这个细节,我们可以如下实现它:

C++20 -std::memmove实现

从C++20开始,std::memmovestd::memcpy是“魔术”,因为它们隐式地开始了目标对象的生存期。std::memmove可以有相同的源和目的地,所以我们可以劫持它的神奇属性并轻松实现std::start_lifetime_as

template<class T>
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T* start_lifetime_as(void* p) noexcept
{
    return std::launder(static_cast<T*>(std::memmove(p, p, sizeof(T)));
}

字符串
之所以这样做是因为:
这两个函数[std::memcpystd::memmove]都在将字符序列复制到目标之前,在目标存储区域中隐式地创建对象。

  • [cstring.syn] p3
    std::memmove将内存区域[p, p + sizeof(T))转换为隐式创建对象的区域。你可能会问这个对象的类型是什么:
    对于被指定为隐式创建对象的每个操作,如果这样做会导致程序具有定义的行为,则该操作在其指定的存储区域中隐式创建并启动零个或多个 * 隐式生存期类型 * 的对象的生存期。
  • [介绍对象]第10页
    如果没有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获取指向新创建的对象的指针。

  • §3.8直接对象创建
template<class T>
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T* start_lifetime_as(void* p) noexcept
{
    // 1. Copy the storage elsewhere.
    std::byte backup[sizeof(T)];
    std::memcpy(backup, p, sizeof(T));
    
    // 2. Use placement new of an array of byte-like type
    //    according to [intro.objec] p13, this implicitly begins the lifetime
    //    of an object within the byte storage.
    //    However, it also turns the memory at p indeterminate.
    new (p) std::byte[sizeof(T)];

    // 3. Copy the storage back to its original location.
    //    This turns the object representation determinate again,
    //    while keeping the implicit object creation at p.
    std::memcpy(p, backup, sizeof(T));

    // 4. Return a laundered pointer.
    //    Because T being at the address that p represents is a
    //    precondition of std::launder, this forces the implicit
    //    object created via placement new to be of type T.
    return std::launder(static_cast<T*>(p));
}

  • 注1:您的实现存在这样的问题,即当您对std::byte[]执行placement new时,内存变得不确定,因此不会保留对象表示。*
  • 注2:C++17版本不完整,因为它不允许启动volatile类型的生存期。

相关问题