我有下面的代码,打算创建一个数组,但没有默认初始化其对象。我想完美地转向放置新的,这似乎发生,但我发现对象的析构函数在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
它表明对象构造一次和破坏两次(这显然是UB),一旦布设函数内部,然后一旦销毁NoInitArray。
问题是“为什么我的Int对象的析构函数在emplace函数中调用”?
编译器,最新的Clang/LLVM在Windhoze上。编辑1:我已经添加移动和拷贝构造函数到Int结构,现在计数匹配,即2结构和2破坏。
编辑2:更改放置新行从new (m_array + idx_) T (std::forward<T> (value_) ...);
到new (m_array + idx_) T (value_ ...);
避免了多余的构造/销毁,无需移动构造函数。
编辑3:只为未来的读者。如上所述,〜NoInitArray()会泄漏内存。在m_array上调用delete是个坏消息,这个调用(在Clang/LLVM中)是m_array [0]的析构函数(但据我现在所理解,这绝不是保证,即UB)。 std :: malloc/std :: free似乎是要走的路,但有人说如果你这样做,所有的地狱都会失败,并且可能会失去一条腿。
一定还要计数复制构造函数调用。 –
@BenVoigt复制构造函数不会调用普通的构造函数吗?如果这是问题,那么这意味着转发不能按预期工作,那么我如何才能完成这项工作,即移动对象而不是复制它? – degski
不,复制构造不链接到默认构造函数。如果您希望移动而不是副本,请提供移动和复制构造函数的用户定义版本,并在每个文件中记录不同的消息。 –