放置新的和完美的转发
Placement New and Perfect Forwarding
我有以下代码打算创建一个数组,但没有默认初始化其对象。我想完美地转发到placement new,这似乎发生了,但我发现对象的析构函数是在template函数内部调用的。
#include <iostream>
#include <memory> // std::uninitialized_copy, std::allocator...
#include <utility> // std::move...
#include <bitset>
struct Int {
int i;
Int ( ) : i ( -1 ) { std::cout << "default constructedn"; }
Int ( const int i_ ) : i ( i_ ) { std::cout << i << " constructedn"; }
Int ( Int && int_ ) : i ( std::move ( int_.i ) ) { std::cout << i << " move constructedn"; }
Int ( const Int & int_ ) : i ( int_.i ) { std::cout << i << " copy constructedn"; }
~Int ( ) { std::cout << i << " destructedn"; i = -1; }
};
template <typename T, size_t S = 64>
class NoInitArray {
std::bitset<S> m_used;
T *m_array = reinterpret_cast < T* > ( ::operator new ( sizeof ( T ) * S ) );
public:
T const &operator [ ] ( const size_t idx_ ) const {
return m_array [ idx_ ];
}
NoInitArray ( ) { }
~NoInitArray ( ) {
for ( size_t idx = 0; idx < S; ++idx ) {
if ( m_used [ idx ] ) {
reinterpret_cast< const T* > ( m_array + idx )->~T ( );
}
}
}
template<typename ...Args>
void emplace ( const size_t idx_, Args &&... value_ ) {
std::cout << "start emplacen";
m_used [ idx_ ] = 1;
new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
std::cout << "end emplacen";
}
};
int main ( ) {
NoInitArray<Int> nia;
nia.emplace ( 0, 0 );
nia.emplace ( 1, 1 );
std::cout << nia [ 1 ].i << std::endl;
nia.emplace ( 2, 2 );
return 0;
}
运行此程序的结果如下:
start emplace
0 constructed
0 move constructed
0 destructed
end emplace
start emplace
1 constructed
1 move constructed
1 destructed
end emplace
1
start emplace
2 constructed
2 move constructed
2 destructed
end emplace
0 destructed
1 destructed
2 destructed
它表明,对象被构造一次,然后被销毁两次(显然是UB),一次在template函数内,然后一次在NoInitArray的销毁时。
问题是"为什么Int对象的析构函数在template函数内部被调用"?
编译器,Windhoze上最新的Clang/LLVM。
编辑1:我已经向Int结构添加了移动和复制构造函数,现在计数匹配,即2个构造和2个破坏。
EDIT2:将Placement新行从new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
更改为new ( m_array + idx_ ) T ( value_ ... );
可以避免多余的构造/破坏,而不需要移动构造函数。
第三版:仅供未来读者阅读。如上所述,~NoInitArray()会泄漏内存。在m_array上调用delete是个坏消息,因为它调用(在Clang/LLVM中)m_array[0]的析构函数(但据我现在所知,这绝不能保证,即UB)。std::malloc/std::free似乎是一条路,但有人说,如果你这样做,一切都会失败,一个人可能会失去一条腿。
"这表明对象被构造一次,被破坏两次"是不正确的。输出X move constructed
应该被包括为一个构造,因此构造是两个。
线路
new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
应该是
new ( m_array + idx_ ) T ( std::forward<Args&&> ( value_ )... );
std::forward<T>(value_)
在T=Int
时调用构造函数,并且这个临时对象被移动,因此有一个额外的移动构造函数调用。
编辑
在编辑2中,您将不再替换不带std::forward
的行。在这种情况下,好吧,但当你像这个一样调用emplace
时,差异就会出现
nia.emplace ( 0, Int(0) );
如果没有std::forward
,new T(value_...)
将调用复制构造函数,而new T(std::forward<Args&&>(value_)...)
将调用移动构造函数。
EDIT-2
应该是new T(std::forward<Args>(value_)...)
。感谢@Constantin Baranov。
我认为构造函数和析构函数是在步骤中调用的:new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... )
中的std::forward<T> ( value_ )
。
std::forward<T>(value_)
将创建一个温度值T.
- 将函数参数完美转发到函数指针:按值传递呢?
- C++20理念:要求表达和完美转发
- 我可以列表初始化 std::vector 并完美转发元素吗?
- 返回值的完美转发?
- 使用衰减与完美转发
- 可变参数模板:将整数参数完美转发到 lambda
- 完美转发C++重载和模板化函子及其参数
- 在完美转发中需要衰减
- C++完美转发:如何避免悬空引用
- 无法理解一段具有完美转发和省略号的C++代码
- 在编写包装现有函数并检查错误的模板函数时,如何使用完美转发?
- 完美转发可变参数模板模板
- 在完美转发函数中公开参数类型,避免代码重复
- 使用完美转发的模板转换构造函数
- 完美转发使用结构化绑定声明的变量
- 完美转发常量参数以进行持续评估
- 使用auto&&完美转发返回值
- 完美转发可变参数模板参数到成员函数
- 无需使用 ODR 即可实现完美转发
- 使用模板类完美转发