为什么 C++17 中没有 std::construct_at?
Why isn't there a std::construct_at in 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)...);
}
这将能够避免这种不完全自然的放置新语法:
auto ptr = construct_at<int>(buf, 1); // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);
std::destroy_at
对直接析构函数调用提供了两个客观改进:
-
它减少了冗余:
T *ptr = new T; //Insert 1000 lines of code here. ptr->~T(); //What type was that again?
当然,我们都希望把它包装在一个
unique_ptr
里,然后完成它,但如果由于某种原因不能发生这种情况,那么T
有一个冗余的元素。如果我们将类型更改为U
,我们现在必须更改析构函数调用,否则事情就会中断。使用std::destroy_at(ptr)
消除了在两个地方更改相同内容的需要。干燥是好的。
-
它使这变得简单:
auto ptr = allocates_an_object(...); //Insert code here ptr->~???; //What type is that again?
如果我们推断出指针的类型,那么删除它就变得有点困难了。你不能做
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();
谁想打字呢?
相比之下,您的假设std::construct_at
没有提供比放置new
客观改进。在这两种情况下,您都必须声明要创建的类型。在这两种情况下,都必须提供构造函数的参数。在这两种情况下,都必须提供指向内存的指针。
所以没有必要通过你假设的std::construct_at
来解决.
客观上,它的能力不如放置新。您可以这样做:
auto ptr1 = new(mem1) T;
auto ptr2 = new(mem2) T{};
这些是不同的。在第一种情况下,对象是默认初始化的,这可能会使其保持未初始化状态。在第二种情况下,对象是值初始化的。
你的假设std::construct_at
不允许你选择你想要的。如果未提供任何参数,它可以具有执行默认初始化的代码,但随后将无法提供值初始化的版本。它可以在没有参数的情况下进行值初始化,但是您不能默认初始化对象。
请注意,C++20 添加了std::construct_at
。但它这样做的原因不是一致性。它们支持编译时内存分配和构造。
您可以在常量表达式中调用"可替换"全局new
运算符(只要您没有实际替换它)。但是放置-new不是一个"可替换"的函数,所以你不能在那里调用它。
早期版本的 constexpr 分配提案依赖于std::allocator_traits<std::allocator<T>>::construct/destruct
.他们后来搬到std::construct_at
作为constexpr
建筑功能,construct
会提到。
因此,当可以提供对新放置的客观改进时,添加了construct_at
。
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...)
进行比较。
有这样的事情,但不像你想象的那样命名:
-
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 定义
有std::allocator_traits::construct
.曾经在std::allocator
中还有一个,但在标准委员会文件D0174R0中被删除了。
我认为应该有一个标准的构造函数。 事实上,libc++在文件stl_construct.h
中有一个作为实现细节。
namespace std{
...
template<typename _T1, typename... _Args>
inline void
_Construct(_T1* __p, _Args&&... __args)
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
...
}
我认为拥有它是否有用,因为它可以让"新安置"成为朋友。 对于需要uninitialized_copy
到默认堆(例如,从std::initializer_list
元素)的仅移动类型来说,这是一个很好的自定义点。
我有自己的容器库,它重新实现(范围)detail::uninitialized_copy
以使用自定义detail::construct
:
namespace detail{
template<typename T, typename... As>
inline void construct(T* p, As&&... as){
::new(static_cast<void*>(p)) T(std::forward<As>(as)...);
}
}
它被声明为仅移动类的友元,以仅允许在放置 new 的上下文中进行复制。
template<class T>
class my_move_only_class{
my_move_only_class(my_move_only_class const&) = default;
friend template<class TT, class...As> friend void detail::construct(TT*, As&&...);
public:
my_move_only_class(my_move_only_class&&) = default;
...
};
construct
似乎没有提供任何语法糖。此外,它的效率低于新放置。绑定到引用参数会导致临时具体化和额外的移动/复制构造:
struct heavy{
unsigned char[4096];
heavy(const heavy&);
};
heavy make_heavy(); // Return a pr-value
auto loc = ::operator new(sizeof(heavy));
// Equivalently: unsigned char loc[sizeof(heavy)];
auto p = construct<heavy>(loc,make_heavy()); // The pr-value returned by
// make_heavy is bound to the second argument,
// and then this arugment is copied in the body of construct.
auto p2 = new(loc) auto(make_heavy()); // Heavy is directly constructed at loc
//... and this is simpler to write!
不幸的是,在调用函数时,没有任何方法可以避免这些额外的复制/移动构造。转发几乎是完美的。
另一方面,图书馆中的construct_at
可以完成标准的图书馆词汇。
- 如何处理来自核心指南检查器的关于gsl::at的静态分析警告
- "CID"在AT+HTTPPARA= "CID" ,1中是什么意思
- 在C++中使用 Catch 测试框架编译错误"error: expected ';' at end of declaration list"
- CMake 用于设定C++标准的设施 "at least C++NN"
- SIGSEGV on Boost UDP 套接字关闭 - tcache_get at malloc.c.
- 使用 mat.at<uchar>(i,j) 的 opencv C++中的矩阵赋值错误
- CMake Error at modules/videoio/cmake/detect_ffmpeg.cmake:16
- 当键值是 std 向量时,为什么使用 at in C++ 访问映射值如此缓慢?
- 两个垫子的 OpenCV 数据是相同的,但使用 Mat::at 检索时的值已损坏
- 使用 at() 访问 std::map 元素是否比运算符 [] 慢?
- 错误:调用 .. at return 语句时没有匹配函数
- OpenCV 像素访问点与 at() - 不同的值
- C++11 功能 std::map::at 编译旧版本的C++
- AT 命令响应解析器
- 在macOS上使用libcurl库编译一个c++at项目
- std::allocator_traits::construct调用了错误的构造函数
- 使用模板时获取"Trigger Breakpoint Error at delete"
- 为什么向量的.at()成员函数返回引用而不是迭代器
- 向量数组"Cannot access memory at address"
- 编译代码时"[Warning] extra tokens at end of"