c++ c++17 copy-elision

c++ - Elision garantizada y llamadas a función encadenada.



c++17 copy-elision (2)

Esto realizará una construcción de 1 copia y 3 construcciones de movimiento.

  1. Haga una copia de a para enlazar a lhs .
  2. Mueve la construcción lhs fuera del primer + .
  3. El retorno del primer + se vinculará al parámetro de valor lhs del segundo + con elision.
  4. El retorno de la segunda lhs incurrirá en la construcción del segundo movimiento.
  5. El retorno de la tercera lhs incurrirá en la construcción del tercer movimiento.
  6. El temporal devuelto desde el tercero + se construirá en la sum .

Para cada una de las construcciones de movimiento descritas anteriormente, hay otra construcción de movimiento que se elimina opcionalmente. Por lo que solo se garantiza tener 1 copia y 6 movimientos. Pero en la práctica, a menos que usted -fno-elide-constructors , tendrá 1 copia y 3 movimientos.

Si no hace referencia a después de esta expresión, puede optimizar aún más con:

X sum = std::move(a) + b + c + d;

dando como resultado 0 copias y 4 movimientos (7 movimientos con -fno-elide-constructors ).

Los resultados anteriores se han confirmado con una X que ha instrumentado copiar y mover constructores.

Actualizar

Si está interesado en diferentes formas de optimizar esto, puede comenzar con la sobrecarga de lhs en X const& X&& :

friend X operator+(X&& lhs, X const& rhs) { lhs += rhs; return std::move(lhs); } friend X operator+(X const& lhs, X const& rhs) { auto temp = lhs; temp += rhs; return temp; }

Esto reduce las cosas a 1 copia y 2 movimientos. Si está dispuesto a restringir a sus clientes para que no vuelvan a obtener el retorno de su + por referencia, entonces puede devolver X&& desde una de las sobrecargas como esta:

friend X&& operator+(X&& lhs, X const& rhs) { lhs += rhs; return std::move(lhs); } friend X operator+(X const& lhs, X const& rhs) { auto temp = lhs; temp += rhs; return temp; }

Bajando a 1 copia y 1 movimiento. Tenga en cuenta que en este último diseño, si su cliente alguna vez hace esto:

X&& x = a + b + c;

entonces x es una referencia colgante (por lo que std::string no hace esto).

Digamos que tengo el siguiente tipo:

struct X { X& operator+=(X const&); friend X operator+(X lhs, X const& rhs) { lhs += rhs; return lhs; } };

Y tengo la declaración (asumiendo que todas las variables nombradas son valores de tipo X ):

X sum = a + b + c + d;

En C ++ 17, ¿cuáles son las garantías que tengo sobre cuántas copias y movimientos realizará esta expresión? ¿Qué pasa con elision no garantizada?


OK, vamos a empezar con esto:

X operator+(X lhs, X const& rhs) { lhs += rhs; return lhs; }

Esto siempre provocará una copia / movimiento del parámetro al objeto de valor de retorno. C ++ 17 no cambia esto, y ninguna forma de elección puede evitar esta copia.

Ahora, veamos una parte de tu expresión: a + b . Dado que el primer parámetro del operator+ se toma por valor, debe copiarse en él. Así que esa es una copia. El valor de retorno se copiará en el valor de retorno. Entonces eso es 1 copia y un movimiento / copia.

Ahora, la siguiente parte: (a + b) + c .

C ++ 17 significa que el prvalue devuelto por a + b se utilizará para inicializar directamente el parámetro del operator+ . Esto no requiere copiar / mover. Pero el valor de retorno de esto se copiará de ese parámetro. Entonces eso es 1 copia y 2 movimientos / copias.

Repita esto para la última expresión, y eso es 1 copia y 3 movimientos / copias. sum se inicializará desde la expresión prvalue, por lo que no es necesario realizar ninguna copia allí.

Su pregunta realmente parece ser si los parámetros permanecen excluidos de la elision en C ++ 17. Porque ya estaban excluidos en versiones anteriores . Y eso no va a cambiar; Las razones para excluir parámetros de elision no han sido invalidadas.

"Elision garantizada" solo se aplica a prvalores. Si tiene un nombre, no puede ser un prvalue.