¿Cuáles son las implicaciones de las garantías de orden de evaluación votadas en C ++ 17 (P0145) en el código C ++ típico?
¿Qué cambia en cosas como las siguientes?
i = 1;
f(i++, i)
y
std::cout << f() << f() << f();
o
f(g(), h(), j());
c++
c++17
operator-precedence
Johan Lundberg
fuente
fuente
Respuestas:
Algunos casos comunes en los que el orden de evaluación no se ha especificado hasta ahora se especifican y son válidos con
C++17
. Algunos comportamientos indefinidos ahora no están especificados.no estaba definido, pero ahora no está especificado. Específicamente, lo que no se especifica es el orden en el que
f
se evalúa cada argumento en relación con los demás.i++
podría evaluarse antesi
, o viceversa. De hecho, podría evaluar una segunda llamada en un orden diferente, a pesar de estar bajo el mismo compilador.Sin embargo, la evaluación de cada argumento debe ejecutarse completamente, con todos los efectos secundarios, antes de la ejecución de cualquier otro argumento. Por lo tanto, puede obtener
f(1, 1)
(segundo argumento evaluado primero) of(1, 2)
(primer argumento evaluado primero). Pero nunca obtendráf(2, 2)
ni nada de esa naturaleza.no se especificó, pero será compatible con la precedencia del operador, de modo que la primera evaluación de
f
será la primera en la secuencia (ejemplos a continuación).todavía tiene un orden de evaluación no especificado de g, h y j. Tenga en cuenta que para
getf()(g(),h(),j())
, las reglas establecen quegetf()
se evaluará antesg, h, j
.También tenga en cuenta el siguiente ejemplo del texto de la propuesta:
El ejemplo proviene de The C ++ Programming Language , 4th edition, Stroustrup, y solía ser un comportamiento no especificado, pero con C ++ 17 funcionará como se esperaba. Hubo problemas similares con las funciones reanudables (
.then( . . . )
).Como otro ejemplo, considere lo siguiente:
#include <iostream> #include <string> #include <vector> #include <cassert> struct Speaker{ int i =0; Speaker(std::vector<std::string> words) :words(words) {} std::vector<std::string> words; std::string operator()(){ assert(words.size()>0); if(i==words.size()) i=0; // Pre-C++17 version: auto word = words[i] + (i+1==words.size()?"\n":","); ++i; return word; // Still not possible with C++17: // return words[i++] + (i==words.size()?"\n":","); } }; int main() { auto spk = Speaker{{"All", "Work", "and", "no", "play"}}; std::cout << spk() << spk() << spk() << spk() << spk() ; }
Con C ++ 14 y antes, podemos (y obtendremos) resultados como
play no,and,Work,All,
en vez de
All,work,and,no,play
Tenga en cuenta que lo anterior es en efecto lo mismo que
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Pero aún así, antes de C ++ 17 no había garantía de que las primeras llamadas llegaran primero a la secuencia.
Referencias: De la propuesta aceptada :
Editar nota: Mi respuesta original fue malinterpretada
a(b1, b2, b3)
. El orden deb1
,b2
,b3
es todavía indeterminado. (gracias @KABoissonneault, todos los comentaristas).Sin embargo, (como se señala @Yakk) y esto es importante: Incluso cuando
b1
,b2
,b3
son expresiones no triviales, cada uno de ellos están completamente evaluados y conectado con los parámetros de la función respectiva antes de que se comenzaron a ser evaluado los otros. El estándar establece esto así:Sin embargo, falta una de estas nuevas oraciones en el borrador de GitHub :
El ejemplo está ahí. Resuelve problemas de hace décadas ( como lo explicó Herb Sutter ) con una excepción de seguridad donde cosas como
f(std::unique_ptr<A> a, std::unique_ptr<B> b); f(get_raw_a(), get_raw_a());
se filtraría si una de las llamadas
get_raw_a()
se lanzara antes de que el otro puntero sin formato estuviera vinculado a su parámetro de puntero inteligente.Como lo señaló TC, el ejemplo es defectuoso ya que la construcción de unique_ptr a partir del puntero sin formato es explícita, lo que evita que se compile. *
También tenga en cuenta esta pregunta clásica (etiquetada C , no C ++ ):
aún no está definido.
fuente
a
, luegob
, luegoc
, luegod
" y luego se muestraa(b1, b2, b3)
, lo que sugiere que todas lasb
expresiones no se evalúan necesariamente en ningún orden (de lo contrario, lo seríaa(b, c, d)
)a(b1()(), b2()())
puede ordenarb1()()
yb2()()
en cualquier orden, pero no puede hacerlob1()
a continuación,b2()()
entoncesb1()()
: ya no entrelazado de sus ejecuciones que pase. En resumen, "8. ORDEN DE EVALUACIÓN ALTERNATIVA PARA LAS LLAMADAS DE FUNCIÓN" fue parte del cambio aprobado.f(i++, i)
estaba indefinido. Ahora no está especificado. El ejemplo de cadena de Stroustrup probablemente no estaba especificado, no estaba indefinido. `f (get_raw_a (), get_raw_a ());` no compilará ya que elunique_ptr
constructor relevante es explícito. Finalmente,x++ + ++x
es indefinido, punto.El intercalado está prohibido en C ++ 17
En C ++ 14, lo siguiente no era seguro:
void foo(std::unique_ptr<A>, std::unique_ptr<B>); foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Hay cuatro operaciones que ocurren aquí durante la llamada a la función
new A
unique_ptr<A>
constructornew B
unique_ptr<B>
constructorEl orden de estos fue completamente indeterminado, por lo que un orden perfectamente válido es (1), (3), (2), (4). Si se seleccionó este orden y (3) se arroja, entonces la memoria de (1) se filtra; aún no hemos ejecutado (2), lo que habría evitado la fuga.
En C ++ 17, las nuevas reglas prohíben el entrelazado. De [intro.execution]:
Hay una nota a pie de página en esa oración que dice:
Esto nos deja con dos ordenamientos válidos: (1), (2), (3), (4) o (3), (4), (1), (2). No se especifica qué orden se toma, pero ambos son seguros. Todos los pedidos en los que (1) (3) ocurren antes de (2) y (4) ahora están prohibidos.
fuente
Encontré algunas notas sobre el orden de evaluación de expresiones:
En P0145R3.Refining Expression Evaluation Order for Idiomatic C ++ he encontrado:
Pero no lo encontré en estándar, sino en estándar que encontré:
Entonces, comparé el comportamiento de acuerdo en tres compiladores para los estándares 14 y 17. El código explorado es:
#include <iostream> struct A { A& addInt(int i) { std::cout << "add int: " << i << "\n"; return *this; } A& addFloat(float i) { std::cout << "add float: " << i << "\n"; return *this; } }; int computeInt() { std::cout << "compute int\n"; return 0; } float computeFloat() { std::cout << "compute float\n"; return 1.0f; } void compute(float, int) { std::cout << "compute\n"; } int main() { A a; a.addFloat(computeFloat()).addInt(computeInt()); std::cout << "Function call:\n"; compute(computeFloat(), computeInt()); }
Resultados (el más consistente es el sonido metálico):
<style type="text/css"> .tg { border-collapse: collapse; border-spacing: 0; border-color: #aaa; } .tg td { font-family: Arial, sans-serif; font-size: 14px; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #333; background-color: #fff; } .tg th { font-family: Arial, sans-serif; font-size: 14px; font-weight: normal; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #fff; background-color: #f38630; } .tg .tg-0pky { border-color: inherit; text-align: left; vertical-align: top } .tg .tg-fymr { font-weight: bold; border-color: inherit; text-align: left; vertical-align: top } </style> <table class="tg"> <tr> <th class="tg-0pky"></th> <th class="tg-fymr">C++14</th> <th class="tg-fymr">C++17</th> </tr> <tr> <td class="tg-fymr"><br>gcc 9.0.1<br></td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> <tr> <td class="tg-fymr">clang 9</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> </tr> <tr> <td class="tg-fymr">msvs 2017</td> <td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> </table>
fuente