11

ベクトル要素の破棄順序は C++ 標準では定義されていないことを知っています ( std::vector の要素の破棄順序を参照)。チェックしたすべてのコンパイラが最初から最後までこの破棄を行うことを確認しました。動的配列と静的配列は逆の順序でそれを行います。この逆の順序は C++ の世界ではよくあります。

厳密に言うと、「コンテナーメンバーは、たとえばメンバー関数の挿入および消去を使用して、任意の順序で構築および破棄できる」ことを知っており、「これらの変更について何らかのログを保持するコンテナー」には投票しません。現在のベクトル デストラクタの実装を要素の前方破壊から後方破壊に変更することに投票するだけです。そして、このルールを C++ 標準に追加するかもしれません。

で、その理由は?配列からベクトルへの変更は、この方法でより安全になります。

実際の例: ミューテックスのロックとロック解除の順序が非常に重要であることは誰もが知っています。ロック解除を確実にするために、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()
4

2 に答える 2

5

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

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

于 2012-06-18T15:20:41.123 に答える
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()

これは意図的にこのように実装されました。ここで定義されている主要な機能は次のとおりです。

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_));
}

このプライベート実装-詳細はsize()、縮小する必要があるときはいつでも呼び出されます。

この目に見える実装の詳細について、ポジティブかネガティブかを問わず、まだフィードバックを受け取っていません。

于 2012-06-19T20:33:14.207 に答える