¿Sería razonable definir el orden de destrucción de los elementos vectoriales?

Sé que el orden de destrucción de elementos vectoriales no está definido por el estándar C ++ (ver Orden de destrucción de elementos de un std :: vector ) y vi que todos los comstackdores que verifiqué hacen esta destrucción de principio a fin, lo que me sorprende bastante desde entonces Las matrices dinámicas y estáticas lo hacen en orden inverso, y este orden inverso es bastante frecuente en el mundo de C ++.

Para ser estricto: sé que “los miembros del contenedor … pueden construirse y destruirse en cualquier orden utilizando, por ejemplo, insertar y borrar funciones de miembro” y no voto por “contenedores para mantener algún tipo de registro sobre estos cambios”. Simplemente votaría para cambiar la implementación actual del destructor vectorial desde la destrucción hacia adelante hasta la destrucción hacia atrás de los elementos, nada más. Y tal vez agregue esta regla al estándar de C ++.

¿Y la razón por la que? El cambio de matrices a vectores sería más seguro de esta manera.

EJEMPLO DE MUNDO REAL: Todos sabemos que el orden de locking y deslocking de exclusión mutua es muy importante. Y para garantizar que se realice el deslocking, se utiliza el patrón ScopeGuard. Entonces el orden de destrucción es importante. Considera este ejemplo. Allí, el cambio de matrices a vectores provoca un punto muerto, solo porque su orden de destrucción es diferente:

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

SALIDA – ver el cambio de orden de destrucción de f1 () a 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() 

Creo que este es otro caso en el que C ++ da a los escritores de comstackdores la flexibilidad de escribir los contenedores más eficaces para su architecture. Requerir la destrucción en un orden en particular podría afectar el rendimiento por conveniencia en algo así como el 0.001% de los casos (en realidad nunca he visto otro ejemplo en el que el orden predeterminado no fuera adecuado). En este caso, ya que vector es datos contiguos, me refiero a la capacidad del hardware para utilizar el almacenamiento en caché anticipado de manera inteligente en lugar de iterar hacia atrás y, probablemente, omitir repetidamente el caché.

Si se requiere un orden de destrucción particular para su instancia de contenedor, el lenguaje le solicita que lo implemente usted mismo para evitar la posibilidad de penalizar a otros clientes de las características estándar.

Fwiw, libc ++ salidas:

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

Fue implementado a propósito de esta manera. La función clave definida aquí es:

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

Este detalle de implementación privado se llama siempre que el size() necesita reducirse.

Todavía no he recibido ningún comentario sobre este detalle de implementación visible, ya sea positivo o negativo.