我有以下代码,意图创建一个数组,但不对其对象进行默认初始化。我希望完美地转发到放置 new,这似乎已经发生,但我发现对象的析构函数在 emplace 函数内被调用。
#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 constructed\n"; }
Int ( const int i_ ) : i ( i_ ) { std::cout << i << " constructed\n"; }
Int ( Int && int_ ) : i ( std::move ( int_.i ) ) { std::cout << i << " move constructed\n"; }
Int ( const Int & int_ ) : i ( int_.i ) { std::cout << i << " copy constructed\n"; }
~Int ( ) { std::cout << i << " destructed\n"; 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 emplace\n";
m_used [ idx_ ] = 1;
new ( m_array + idx_ ) T ( std::forward<T> ( value_ ) ... );
std::cout << "end emplace\n";
}
};
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
它显示对象被构造一次并被析构两次(这显然是未定义行为),一次在emplace函数内部,然后在NoInitArray的销毁时再次销毁。
问题是:“为什么我的Int对象的析构函数在emplace函数内部调用?”
编译器使用最新的Clang/LLVM,运行在Windhoze上。
EDIT1:我将移动和拷贝构造函数添加到Int结构体中,现在数量匹配,即2个构造和2个析构。
EDIT2: 将放置new行从
new (m_array + idx_) T(std::forward<T>(value_)...);
更改为new (m_array + idx_) T(value_...);
可以避免多余的构造/析构,不需要移动构造函数。EDIT3:仅供未来读者参考。如上所述,~NoInitArray()泄漏内存。调用delete m_array也是坏消息,因为它调用(在Clang/LLVM中)m_array [0]的析构函数(但据我现在所理解的,这无论如何都不能保证,即未定义行为)。std::malloc / std::free似乎是正确的方法,但有人说,如果您这样做,所有地狱都会敞开大门,并且可能失去一条腿。