2012-06-18 38 views
10

我知道向量元素破壞順序沒有用C++定義的標準(見Order of destruction of elements of an 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(); 
} 

輸出 - 看到從破壞秩序的變化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

恕我直言銷燬順序應該不會影響,如果軟件設計的很好。當調用析構函數時,這意味着對象不再被使用或不需要。在銷燬它們之前,你應該確保你的對象處於一致的狀態(在這種情況下不再使用)。 – m0skit0

+0

我們也都知道,當執行順序很重要時,將它們放在任何容器中並讓生成的代碼銷燬並不是一個好主意。 //嘲諷注意:我對「我們都知道」陳述有點懷疑 – stefaanv

+0

可能你沒有閱讀就回答。 n ScopeGuard(http://stackoverflow.com/questions/48647/does-scopeguard-use-really-lead-to-better-code)我在這裏使用的銷燬順序很重要。這就是我使用這個例子的原因。 – PiotrNycz

回答

4

我認爲這是另一種C++爲編譯器編寫者提供了爲他們的架構編寫最高性能容器的靈活性。以特定的順序銷燬可能會損害性能,例如在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的主要功能是:

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()需要收縮。

我還沒有收到有關此可見的實現細節的任何反饋,無論是正面還是負面的。

+1

很高興知道存在這樣的實現。 – PiotrNycz

+0

您如何看待Mark對前向破壞的理由:使用「硬件智能地利用預讀緩存的能力,而不是向後迭代並可能重複丟失緩存」。 libC++聲稱性能良好... – PiotrNycz

+0

這是一種可能性。但我沒有注意到這種差異。我也沒有找到它。我的客戶也沒有抱怨這方面的性能問題(我在其他領域已經得到了性能方面的投訴 - 有些已經提到,有些仍然在我的待辦事項上)。 –

相關問題