2012-06-18 4 views
10

ベクトル要素の破壊順序はC++標準では定義されていません(Order of destruction of elements of an std::vector参照)、私が調べたすべてのコンパイラが最初から最後までこの破壊を行うことがわかりました。これはダイナミックな配列と静的な配列が逆になるのでこの逆の順序はC++の世界では非常に頻繁に起こります。ベクトル要素の破壊順序を定義することは合理的でしょうか?

厳密に言うと、私は、「コンテナのメンバーは、例えば、メンバーの挿入と削除の機能を使って、どのような順序でも構築および破棄できます」と知っています。変更 "。私は、現在のベクトルデストラクタの実装を前方破壊から要素の後方破壊に変えることに投票するだけです。 C++標準にこのルールを追加することもできます。

そしてなぜですか?配列からベクトルへの変更はこのように安全です。

実際の世界の例: mutexesのロックとロック解除の順序は非常に重要です。ロック解除が確実に行われるように、ScopeGuardパターンが使用されます。それから破壊命令が重要です。この例を考えてみましょう。そこ - ベクトル原因のデッドロックへの配列からの切り替え - 彼らの破壊の順序が異なるという理由だけで:

class mutex { 
public: 
    void lock() { cout << (void*)this << "->lock()\n"; } 
    void unlock() { cout << (void*)this << "->unlock()\n"; } 
}; 

class lock { 
    lock(const mutex&); 
public: 
    lock(mutex& m) : m_(&m) { m_->lock(); } 
    lock(lock&& o) { m_ = o.m_; o.m_ = 0; } 
    lock& operator = (lock&& o) { 
     if (&o != this) { 
      m_ = o.m_; o.m_ = 0; 
     } 
     return *this; 
    } 
    ~lock() { if (m_) m_->unlock(); } 
private: 
    mutex* m_; 
}; 

mutex m1, m2, m3, m4, m5, m6; 

void f1() { 
    cout << "f1() begin!\n"; 
    lock ll[] = { m1, m2, m3, m4, m5 }; 
    cout <<; "f1() end!\n"; 
} 

void f2() { 
    cout << "f2() begin!\n"; 
    vector<lock> ll; 
    ll.reserve(6); // note memory is reserved - no re-assigned expected!! 
    ll.push_back(m1); 
    ll.push_back(m2); 
    ll.push_back(m3); 
    ll.push_back(m4); 
    ll.push_back(m5); 
    cout << "f2() end!\n"; 
} 

int main() { 
    f1(); 
    f2(); 
} 

をOUTPUT - f1からf2に破壊順序の変更を()(参照)

f1() begin! 
0x804a854->lock() 
0x804a855->lock() 
0x804a856->lock() 
0x804a857->lock() 
0x804a858->lock() 
f1() end! 
0x804a858->unlock() 
0x804a857->unlock() 
0x804a856->unlock() 
0x804a855->unlock() 
0x804a854->unlock() 
f2() begin! 
0x804a854->lock() 
0x804a855->lock() 
0x804a856->lock() 
0x804a857->lock() 
0x804a858->lock() 
f2() end! 
0x804a854->unlock() 
0x804a855->unlock() 
0x804a856->unlock() 
0x804a857->unlock() 
0x804a858->unlock() 
+1

IMHOソフトウェアがうまく設計されているかどうかは、破壊の順序は関係ありません。デストラクタが呼び出されると、これはオブジェクトがもはや使用されていないか、必要でないことを意味します。オブジェクトを破壊する前に、オブジェクトが一貫した状態(この場合はもう使用されていない状態)にあることを確認する必要があります。 – m0skit0

+0

実行の順序が重要なときには、コンテナに入れて生成されたコードを破棄することはお勧めできません。 //皮肉の告知:「私たち全員が知っている」ステートメントで少し疑わしいです。 – stefaanv

+0

おそらくあなたは読まなくても答えました。 n ScopeGuard(http://stackoverflow.com/questions/48647/does-scopeguard-use-really-lead-to-better-code)私がここで使用したのは、破壊命令です。だから私はこの例を使ったのです。 – PiotrNycz

答えて

4

私はこれがあると思いますC++のもう1つのケースは、コンパイラーライターに、そのアーキテクチャーのために最もパフォーマンスの高いコンテナーを書く柔軟性を与えることです。特定の順序で破壊を要求すると、ケースの0.001%のような利便性のためにパフォーマンスが損なわれる可能性があります(私は実際にデフォルトの順序が適切でない別の例は見ていません)。この場合、vectorは連続したデータであるため、逆方向の反復処理やキャッシュの不足を繰り返す代わりに、先読みキャッシュをインテリジェントに活用するハードウェアの能力を指しています。

コンテナインスタンスに特定の破棄順序が必要な場合は、標準機能の他のクライアントに不利な影響を及ぼす可能性を回避するために、独自に実装するように求められます。

+0

この回答をありがとう。私は実際に前方と後方の破壊の間にパフォーマンスの違いがないと思っていました。 – PiotrNycz

+0

Mark - あなたの答えによると - 配列やメンバー変数の逆順でデストラクタを呼び出すC++ルールは、この破壊は順方向でこれほど効率的ではありませんか? – PiotrNycz

+0

@ user1463922パフォーマンスに影響がある可能性があります。確かにメンバ変数の場合は、一定の順序で構築/破壊することが望ましいです。コンテナの場合は、別のケースと選択肢です。 –

4

FWIW、libc++出力:

f1() begin! 
0x1063e1168->lock() 
0x1063e1169->lock() 
0x1063e116a->lock() 
0x1063e116b->lock() 
0x1063e116c->lock() 
f1() end! 
0x1063e116c->unlock() 
0x1063e116b->unlock() 
0x1063e116a->unlock() 
0x1063e1169->unlock() 
0x1063e1168->unlock() 
f2() begin! 
0x1063e1168->lock() 
0x1063e1169->lock() 
0x1063e116a->lock() 
0x1063e116b->lock() 
0x1063e116c->lock() 
f2() end! 
0x1063e116c->unlock() 
0x1063e116b->unlock() 
0x1063e116a->unlock() 
0x1063e1169->unlock() 
0x1063e1168->unlock() 

それは意図的にこのように実装されました。 hereを定義された主な機能は次のとおりです。size()を縮小する必要があるとき

template <class _Tp, class _Allocator> 
_LIBCPP_INLINE_VISIBILITY inline 
void 
__vector_base<_Tp, _Allocator>::__destruct_at_end(const_pointer __new_last, false_type) _NOEXCEPT 
{ 
    while (__new_last != __end_) 
     __alloc_traits::destroy(__alloc(), const_cast<pointer>(--__end_)); 
} 

このプライベート実装の詳細が呼び出されます。

私はこの可視的な実装の詳細について、肯定的または否定的なフィードバックをまだ受け取っていません。

+1

このような実装が存在することを知ってうれしいです。 – PiotrNycz

+0

Mark for forward destructionの理論的根拠について、「キャッシュを見逃したり、繰り返しキャッシュを見逃したりするのではなく、先読みキャッシュをインテリジェントに活用するハードウェアの能力」を使用することについてどう思いますか? libC++はパフォーマンスが良いと主張しています... – PiotrNycz

+0

これは可能です。しかし、私はそのような違いに気づいていません。私はそれを探したこともありません。私の顧客の誰もこの分野のパフォーマンス問題を訴えていません(私は他の分野でパフォーマンス苦情を受けています - すでに対応しているものもあれば、やるべきものもあります)。 –

関連する問題