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

输出 - 查看从 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++ 的另一个案例,它让编译器编写者可以灵活地为其架构编写性能最高的容器。在 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 回答