versiones guia español descargar actualizar c++ c++11 templates exception-handling typetraits

c++ - guia - qgis manual



Usar rasgos de tipo C++ 11 para proporcionar implementaciones alternativas en línea (6)

¿Es razonable el siguiente patrón de código cuando se utilizan rasgos en el código de plantilla donde ambas implementaciones alternativas siempre son compilables?

Leer el código parece más claro que hacer otros chanchullos para compilar condicionalmente (pero tal vez no estoy lo suficientemente familiarizado con esos chanchullos).

template<typename T> class X { void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value) { if (std::is_nothrow_copy_constructible<T>::value) { // some short code that assumes T''s copy constructor won''t throw } else { // some longer code with try/catch blocks and more complexity } } // many other methods };

(La complejidad añadida es en parte para proporcionar la fuerte garantía de excepción).

Sé que este código funcionará , pero ¿es razonable esperar que el compilador elimine las ramas constantes-falsas y haga incrustaciones, etc. para el caso no-except (donde es mucho más simple que el otro caso)? Espero algo que sea tan eficiente en el caso de que exista la excepción de escribir el método con solo ese primer bloque como cuerpo (y viceversa, aunque estoy menos preocupado por el caso complejo).

Si esta no es la forma correcta de hacerlo, ¿puede alguien por favor aclararme la sintaxis recomendada?


¿Es razonable esperar que el compilador elimine las ramas constantes-falsas y realice la alineación, etc. para el caso no-exceptual [...]?

Sí. Dicho esto, la rama constante-falsa tiene que ser instanciada, lo que puede o no causar que el compilador crea una instancia de un grupo de símbolos que no necesita (y luego necesita confiar en el vinculador para eliminarlos).

Todavía me quedaría con los chanchullos de SFINAE (en realidad, el despacho de etiquetas), lo cual se puede hacer realmente fácil en C ++ 11.

template<typename T> class X { void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value) { do_something_impl(std::is_nothrow_copy_constructible<T>() ); } void do_something_impl( std::true_type /* nothrow_copy_constructible */ ) { // some short code that assumes T''s copy constructor won''t throw } void do_something_impl( std::false_type /* nothrow_copy_constructible */) { // some longer code with try/catch blocks and more complexity } // many other methods };

Si va a verificar por nothrow_copy_constructor en todos los otros métodos, puede considerar la especialización de toda la clase:

template<typename T, class = std::is_nothrow_copy_constructible_t<T> > class X { //throw copy-ctor implementation }; template<typename T> class X<T, std::true_type> { // noexcept copy-ctor implementation };


¿Es razonable esperar que el compilador elimine las ramas constantes-falsas?

Sí, la eliminación del código muerto es una de las optimizaciones más simples.

... y hacer inline, etc. para el caso no exceptuado?

Mi primer impulso fue responder "No, no puede confiar en eso, ya que depende de dónde se encuentra el pase de entrada en el flujo de optimización relativo al paso de eliminación del código muerto" .

Pero después de una mayor reflexión, no puedo ver por qué un compilador maduro con un nivel de optimización lo suficientemente alto no realizará la eliminación del código inactivo antes y después del paso de creación. Entonces esta expectativa también debería ser razonable.

Sin embargo, adivinar con respecto a las optimizaciones nunca es algo seguro. Vaya por la implementación simple y llegue al código de funcionamiento correcto. Luego mida su desempeño y verifique si sus suposiciones son ciertas. Si no lo fueran, la reelaboración de la implementación para su situación no tomará mucho más tiempo que si hubiera estado en el camino garantizado desde el principio.


[...] ¿es razonable esperar que el compilador elimine las ramas constantes-falsas y haga la alineación, etc. para el caso no-except (donde es mucho más simple que el otro caso)?

Podría ser, pero no confiaría en eso porque no puedes controlarlo.

Si desea eliminar el if/else , puede modificar el tipo de devolución y limpiar el calificador no noexcept .
Como ejemplo:

template<typename T> class X { template<typename U = T> std::enable_if_t<std::is_nothrow_copy_constructible<T>::value> do_something() noexcept(true) {} template<typename U = T> std::enable_if_t<not std::is_nothrow_copy_constructible<T>::value> do_something() noexcept(false) {} };

Los inconvenientes son que ahora tiene una plantilla de funciones de dos miembros.
No estoy seguro de que se ajuste a sus requisitos.

Si tiene permiso para usar funciones de C ++ 17, if constexpr es probablemente el camino a seguir y ya no tiene que dividir su método en dos funciones miembro.

Otro enfoque podría basarse en el envío de etiquetas a la no noexcept de su tipo.
Como ejemplo:

template<typename T> class X { void do_something(std::true_type) noexcept(true) {} void do_something(std::false_type) noexcept(false) {} void do_something() noexcept(do_something(std::is_nothrow_copy_constructible<T>{})) { do_something(std::is_nothrow_copy_constructible<T>{}); } };

Lo sé, sfinae no es un verbo, pero para mejorar algo suena muy bien.


pero es razonable esperar que el compilador elimine las ramas constante-falsa

No. Todas las ramas serán evaluadas por el compilador. Puede intentar usar if constexpr desde c ++ 17.

Lo que estás tratando de lograr es SFINAE.


Cada compilador maduro elimina el código muerto. Cada compilador maduro detecta ramas constantes y los códigos muertos la otra rama.

Puede crear una función con una docena de argumentos de plantilla que usa ingenuamente if comprueba en su cuerpo y mira el resultado asintomático; no habrá problemas.

Si hace cosas como crear variables static o símbolos thread_local o instanciar, estas son más difíciles de eliminar.

Inlinear es un poco complicado, porque los compiladores tienden a renunciar a la línea en algún momento; cuanto más complejo es el código, más probabilidades hay de que el compilador se rinda antes de delimitarlo.

En C ++ 17 puede actualizar su versión if es constexpr . Pero en C ++ 14 y 11, su código funcionará bien. Es más simple y fácil de leer que las alternativas.

Es algo frágil, pero si se rompe normalmente lo hace en tiempo de compilación de una manera ruidosa.


Podría intentar implementar constexpr_if usted mismo. La solución c ++ 11 podría verse de la siguiente manera:

#include <iostream> #include <type_traits> template <bool V> struct constexpr_if { template <class Lambda, class... Args> static int then(Lambda lambda, Args... args) { return 0; } }; template <> struct constexpr_if<true> { template <class Lambda, class... Args> static auto then(Lambda lambda, Args... args) -> decltype(lambda(args...)) { return lambda(args...); } static int then(...) { return 0; } }; struct B { B() {} B(const B &) noexcept {} void do_something() { std::cout << "B::do_something()" << std::endl; } }; struct C { C() {} C(const C &) noexcept {} void do_something_else() { std::cout << "C::do_something_else()" << std::endl; } }; struct D { D() {} D(const D &) throw(int) {} void do_something_else() { std::cout << "D::do_something_else()" << std::endl; } }; template <class T> struct X { void do_something() { T t; constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](B &b) { b.do_something(); }, t); constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](C &c) { c.do_something_else(); }, t); constexpr_if<!std::is_nothrow_copy_constructible<T>::value>::then([](D &d) { d.do_something_else(); }, t); } }; int main() { X<B> x; x.do_something(); X<C> xx; xx.do_something(); X<D> xxx; xxx.do_something(); }

Salida:

B::do_something() C::do_something_else() D::do_something_else()