定义向量元素的破坏顺序是否合理?

时间:2012-06-18 14:40:28

标签: c++ vector standards destruction

我知道向量元素销毁顺序不是由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()

2 个答案:

答案 0 :(得分:4)

我认为这是C ++的另一个案例,它使编译器编写者能够灵活地为其架构编写性能最高的容器。要求按特定顺序进行破坏可能会损害性能以方便0.001%的情况(我实际上从未见过另一个默认顺序不合适的例子)。在这种情况下,因为vector是连续数据,所以我指的是硬件能够智能地利用先行缓存而不是向后迭代,并且可能反复丢失缓存。

如果您的容器实例需要特定的销毁订单,则该语言会要求您自行实施,以避免对标准功能的其他客户造成损害。

答案 1 :(得分: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()需要缩小,就会调用此私有实现细节。

我尚未收到有关此可见实施细节的任何反馈,无论是积极的还是消极的。