2016-06-12 27 views
4

我有下面的代码,打算创建一个数组,但没有默认初始化其对象。我想完美地转向放置新的,这似乎发生,但我发现对象的析构函数在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似乎是要走的路,但有人说如果你这样做,所有的地狱都会失败,并且可能会失去一条腿。

+1

一定还要计数复制构造函数调用。 –

+0

@BenVoigt复制构造函数不会调用普通的构造函数吗?如果这是问题,那么这意味着转发不能按预期工作,那么我如何才能完成这项工作,即移动对象而不是复制它? – degski

+0

不,复制构造不链接到默认构造函数。如果您希望移动而不是副本,请提供移动和复制构造函数的用户定义版本,并在每个文件中记录不同的消息。 –

回答

4

“它表明对象构造一次并被破坏两次”是不正确的。输出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了。在这种情况下,OK,但是当你调用出现差异的emplace这样

nia.emplace (0, Int(0)); 

没有std::forward,​​会调用拷贝构造函数,而new T(std::forward<Args&&>(value_)...)将调用移动的构造。

EDIT 2

应该new T(std::forward<Args>(value_)...)。感谢@Constantin Baranov。

+0

谢谢你,正如我的EDIT2所示,在这种情况下,它也可以像我在那里描述的那样工作。这是(我)的做法是否正确或者它是否属于非平凡的情况(不同于此)必须使用您的解决方案?结果似乎是一样的,没有调用移动构造函数AFAICS。 – degski

+0

@degski更新了我的文章。我还建议在其他问题或网站上查找有关'std :: forward'的更多细节。 – neuront

+0

棘手的东西,引用“...但用C++,你可以吹你的腿...”想到的。用c + + 11看来你可以吹捧两条腿。谢谢。 – degski

4

我认为在步骤std::forward<T> (value_)new (m_array + idx_) T (std::forward<T> (value_) ...)中调用构造函数和析构函数。

std::forward<T>(value_)将创建一个临时值T.

+0

我只能接受一个答案,神经的答案稍微完整一些,所以我接受了他,谢谢你的答复。 – degski