为什么C++ 17中没有std :: construct_at?

Dan*_*ica 58 c++ placement-new c++17

C++ 17补充说std::destroy_at,但没有任何std::construct_at对应物.这是为什么?难道不能像下面这样简单地实现吗?

template <typename T, typename... Args>
T* construct_at(void* addr, Args&&... args) {
  return new (addr) T(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)

这样可以避免不完全自然的放置新语法:

auto ptr = construct_at<int>(buf, 1);  // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 40

std::destroy_at 对直接析构函数调用提供了两个客观的改进:

  1. 它减少了冗余:

    T *ptr = new T;
    //Insert 1000 lines of code here.
    ptr->~T(); //What type was that again?
    
    Run Code Online (Sandbox Code Playgroud)

    当然,我们都希望将它包装成一个unique_ptr并完成它,但如果由于某种原因不能发生,那么放置T一个冗余元素.如果我们将类型更改为U,我们现在必须更改析构函数调用或事务中断.使用std::destroy_at(ptr)无需在两个地方更改相同的内容.

    干很好.

  2. 它使这很容易:

    auto ptr = allocates_an_object(...);
    //Insert code here
    ptr->~???; //What type is that again?
    
    Run Code Online (Sandbox Code Playgroud)

    如果我们推断出指针的类型,那么删除它会变得很难.你做不到ptr->~decltype(ptr)(); 因为C++解析器不能以这种方式工作.不仅如此,decltype将类型推断为指针,因此您需要从推导类型中删除指针间接.引导您:

    auto ptr = allocates_an_object(...);
    //Insert code here
    using delete_type = std::remove_pointer_t<decltype(ptr)>;
    ptr->~delete_type();
    
    Run Code Online (Sandbox Code Playgroud)

    和谁愿意键入

相比之下,您的假设std::construct_at没有提供关于放置的客观改进new.您必须在两种情况下都说明您正在创建的类型.在两种情况下都必须提供构造函数的参数.在两种情况下都必须提供指向存储器的指针.

所以你的假设不需要解决std::construct_at.

并且客观上不如放置新的能力.你可以这样做:

auto ptr1 = new(mem1) T;
auto ptr2 = new(mem2) T{};
Run Code Online (Sandbox Code Playgroud)

这些是不同的.在第一种情况下,对象是默认初始化的,这可能会使其未初始化.在第二种情况下,对象是值初始化的.

假想的std::construct_at 不能让你选择你想要哪一个.如果您不提供参数,它可以具有执行默认初始化的代码,但是它将无法提供值初始化的版本.并且它可以在没有参数的情况下初始化值,但是您无法默认初始化对象.

  • 遗憾的是,标准委员会不仅仅增加了对语法`ptr-> ~auto();`的支持......但也许那里有一个最令人烦恼的解析问题. (4认同)
  • @Sneftel我怀疑这是一个MVP问题.我想这是1)委员会对新语法的保守方法和2)是否"auto"指的是指针的类型或它的构造类型(或者如果`ptr`是迭代器的类型的问题)的组合.实际上是一个迭代器!). (3认同)
  • @Sneftel:问题实际上是"分配和构造"和"内存构造"都使用相同的关键字和一般语法:`new`.相比之下,"destroy and deallocate"和"destroy"使用不同的语法和不同的关键字:`delete`与调用析构函数.因此,虽然`ptr-> auto()`可以解决这个问题,但基本问题是C++与此不协调.对于一致性来说,最好有一个placement -'relete`类型的东西. (2认同)

Mar*_*k R 13

有这样的事情,但没有像你期望的那样命名:

  • uninitialized_copy 将一系列对象复制到未初始化的内存区域

  • uninitialized_copy_n (C++ 11)将许多对象复制到未初始化的内存区域(函数模板)

  • uninitialized_fill 将对象复制到未初始化的内存区域,由范围(函数模板)定义

  • uninitialized_fill_n 将对象复制到未初始化的内存区域,由开始和计数(函数模板)定义
  • uninitialized_move (C++ 17)将一系列对象移动到未初始化的内存区域(函数模板)
  • uninitialized_move_n (C++ 17)将许多对象移动到未初始化的内存区域(函数模板)
  • uninitialized_default_construct (C++ 17)通过默认初始化在未初始化的内存区域中构造对象,由范围(函数模板)定义
  • uninitialized_default_construct_n (C++ 17)通过默认初始化在未初始化的内存区域中构造对象,由start和count(函数模板)定义
  • uninitialized_value_construct (C++ 17)通过值初始化在未初始化的内存区域中构造对象,由范围(函数模板)定义
  • uninitialized_value_construct_n (C++ 17)通过未初始化的内存区域中的值初​​始化构造对象,由start和count定义

  • 这毫无意义,-1.这些都是范围算法.就像我们有[`std :: destroy()`](https://en.cppreference.com/w/cpp/memory/destroy)这样的范围破坏算法一样.我们谈论的是单一建筑 - 没有N种建筑.或者更确切地说,存在变体(list-init,paren-init,default-init),但算法不能捕获它.见尼科尔的回答. (7认同)
  • @MarekR如何在转发参数时使用`std :: uninitialized_move`构造单个对象?你会用什么作为`std :: uninitialized_move`的第三个参数,它代表一个目标迭代器? (3认同)

小智 9

std::allocator_traits::construct.标准委员会文件D0174R0中曾经有过一次std::allocator,但已被删除.

  • @KonradRudolph:或者你可以调用`std :: destroy_at`. (2认同)
  • @KonradRudolph:我的观点是没有冗余.他们通过两种不同的机制做了两件不同的事情,出于两个不同的原因......在这个*特定的情况下恰好是同样的事情. (2认同)

Dav*_*one 6

std::construct_at已添加到C ++ 20。这样做的文件是More constexpr容器。据推测,与C ++ 17中的新增功能相比,这没有看到足够的优势,但是C ++ 20改变了一切。

建议添加此功能的目的是为了支持constexpr内存分配,包括std::vector。这要求能够将对象构造到分配的存储中。然而,在以下方面只是简单的放置新的交易void *,不是T *constexpr评价目前无法访问原始存储,委员会希望保持这种状态。库函数std::construct_at添加了一个类型化的接口constexpr T * construct_at(T *, Args && ...)

这还具有不需要用户指定正在构造的类型的优点。它是根据指针的类型推导出来的。正确地将放置位置称为“ new”的语法有点可怕且违反直觉。std::construct_at(ptr, args...)与比较::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...)