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

Pio*_*ycz 11 c++ standards vector destruction

我知道向量元素破坏顺序不是由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();
}
Run Code Online (Sandbox Code Playgroud)

输出 - 查看从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()
Run Code Online (Sandbox Code Playgroud)

Mar*_*k B 5

我认为这是C ++的另一种情况,它使编译器作者可以灵活地为其体系结构编写性能最高的容器。在0.001%的情况下,按特定顺序要求销毁可能会损害性能,以方便使用(实际上,我从未见过另一个示例,其中默认顺序不合适)。在这种情况下,由于vector数据是连续的,因此我指的是硬件能够智能地使用超前缓存而不是向后迭代并可能反复丢失缓存的能力。

如果您的容器实例需要特定的销毁顺序,则该语言会要求您自己实施该销毁顺序,以避免潜在地惩罚其他使用标准功能的客户。


How*_*ant 5

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()
Run Code Online (Sandbox Code Playgroud)

它是有目的地以这种方式实施的。这里定义的关键函数是:

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_));
}
Run Code Online (Sandbox Code Playgroud)

size()每当需要缩小时就会调用这个私有实现细节。

我还没有收到任何关于这个可见实施细节的反馈,无论是积极的还是消极的。