2016-06-12 4 views
4

私は配列を作成しようとしているが、オブジェクトのデフォルトの初期化を行わないコードを持っています。私は完全に新しい配置に進めたいと思いますが、オブジェクトのデストラクタはemplace関数の中で呼び出されています。次いで配置の新規および完璧な転送

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である)一度構築し、二回破壊されていることを示しており、以下のよう

#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; 
} 

このプログラムを実行した結果でありますNoInitArrayの破壊時に一度。

質問は「私のIntオブジェクトのデストラクターがemplace関数の中で呼び出されたのはなぜですか?

コンパイラ、最新のClang/LLVM on Windhoze。

EDIT1:Int構造体にmoveコンストラクタとコピーコンストラクタを追加しました。これでカウントが一致するようになりました(つまり、2回の構造と2回の破壊)。

EDIT2:配置を変更するnew (m_array + idx_) T (std::forward<T> (value_) ...);からnew (m_array + idx_) T (value_ ...);への改行は、移動コンストラクタを必要とせずに余分な構築/破壊を回避します。

EDIT3:将来の読者のためだけです。上記のように、〜NoInitArray()はメモリをリークします。 m_arrayのdeleteを呼び出すことは悪いニュースであり、m_array [0]のデストラクタ(Clang/LLVM)(これまでのところ理解していない限り、つまりUBは保証されていません)です。 std :: malloc/std :: freeは行く方法に見えますが、もしあなたがそうすれば、すべての地獄が失われ、1人は足を失うかもしれません。

+1

コピーコンストラクタコールも必ずカウントしてください。 –

+0

@BenVoigtコピーコンストラクタは通常のコンストラクタを呼び出しませんか?これが問題であれば、これは転送が意図したとおりに機能しないことを意味します。この作業を行うにはどうすればいいですか? – degski

+0

いいえ、コピーコンストラクションはデフォルトのコンストラクタにチェーンされません。コピーではなく移動を期待している場合は、移動コンストラクタとコピーコンストラクタの両方のユーザ定義バージョンを提供し、それぞれに異なるメッセージを記録します。 –

答えて

4

"これは、オブジェクトが一度構築され、2回破壊されたことを示しています"が真ではありません。出力X move constructedは1つの構成として含める必要があります。そのため、構成は2回になります。

new (m_array + idx_) T (std::forward<T> (value_) ...); 

ラインは

new (m_array + idx_) T (std::forward<Args&&> (value_)...); 

std::forward<T>(value_)はコンストラクタT=Intを呼び出し、この一時的なオブジェクトが移動されているので、余分な移動のコンストラクタ呼び出しがあるはずです。

EDITあなたの編集2では、あなたはもう std::forwardずにラインを交換してください。あなたは std::forwardがなければ、この

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

ようemplaceを呼び出すときに、この場合には、[OK]を、しかし、違いが出てくる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

私は1つの答えしか受け入れることができませんが、ニューロンの答えはやや完成しています。 – degski

関連する問題