2012-06-18 40 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

这是一种可能性。但我没有注意到这种差异。我也没有找到它。我的客户也没有抱怨这方面的性能问题(我在其他领域已经得到了性能方面的投诉 - 有些已经提到,有些仍然在我的待办事项上)。 –

相关问题