lenguaje - programa c++
¿Por qué es(a=b)=c sintaxis legal en C++? (4)
¿Cómo funciona una declaración como (a = b) = c;
trabajar en C ++, suponiendo que a
, c
son int
s o cualquier otro tipo primitivo?
Informalmente, en C ++, para tipos incorporados, el resultado de a = b
es una referencia a a
; puede asignar un valor a esa referencia, al igual que con cualquier otra referencia. Entonces, (a = b) = c
asigna el valor de b
a a
, y luego asigna el valor de c
a a
.
Para los tipos definidos por el usuario esto puede no aplicarse, aunque la expresión habitual es que un operador de asignación devuelva una referencia al argumento de la izquierda, por lo que el comportamiento de los tipos definidos por el usuario imita el comportamiento de los tipos incorporados:
struct S {
S& operator=(const S& rhs) {
return *this;
}
};
Ahora, S a, b, c; (a = b) = c;
S a, b, c; (a = b) = c;
significa llamar a.operator=(b)
, que devuelve una referencia a a
; luego llame a S::operator=
en ese resultado c
, llamando efectivamente a.operator=(c)
.
La expresión de asignación a = b
no es un valor l en C, pero está en C ++:
C11, 6.5.14 (Operadores de asignación):
Un operador de asignación almacena un valor en el objeto designado por el operando de la izquierda. Una expresión de asignación tiene el valor del operando izquierdo después de la asignación, pero no es un valor l .
C ++ 14, 5.18 [expr.ass] (Asignación y operadores de asignación compuesta):
El operador de asignación (
=
) y los operadores de asignación compuesta todos agrupan de derecha a izquierda. Todos requieren un valor l modificable como su operando izquierdo y devuelve un valor l referente al operando de la izquierda.
En la evolución de C ++ desde C, varias expresiones se hicieron "lvalue-aware", por así decirlo, porque los valores l son mucho más importantes en C ++ que en C. En C, todo es trivial (trivialmente copiable y trivialmente destructible, todo en el palabras de C ++), por lo que las conversiones lvalue-a-rvalue (o "conversiones lvalue", como las llama C) no son dolorosas. En C ++, la copia y la destrucción son conceptos no triviales, y al hacer que las expresiones conserven el valor l, se puede evitar una gran cantidad de copiado y destrucción que nunca fue necesario para empezar.
Otro ejemplo es la expresión condicional ( a ? b : c
), que no es un valor l en C, pero puede ser un valor l en C ++.
Otro interesante artefacto de esta evolución del lenguaje es que C tiene cuatro duraciones de almacenamiento bien definidas (automática, estática, local de subprocesos, dinámica), pero en C ++ esto se vuelve más confuso, ya que los objetos temporales son un concepto no trivial en C ++ que casi requiere su propia duración de almacenamiento. (Por ejemplo, Clang internamente tiene una quinta duración de almacenamiento de "expresión completa" ). Los temporarios son, por supuesto, el resultado de la conversión lvalue-a-rvalue, por lo que al evitar la conversión, hay una cosa menos de qué preocuparse.
(Tenga en cuenta que toda esta discusión solo se aplica a las respectivas expresiones del lenguaje central. C ++ también tiene la función independiente y no relacionada de la sobrecarga del operador , que produce expresiones de llamada de función, que tienen todas las semánticas habituales de llamadas a función y no tienen nada que ver con operadores, excepto la sintaxis. Por ejemplo, puede definir un operator=
sobrecargado que devuelve un valor prvalue o void
si lo desea).
Lo siguiente es un poco de especulación, así que por favor corrígeme si me equivoco.
Cuando inventaron la sobrecarga del operador, tuvieron que buscar una forma general estándar de un operador de asignación para cualquier clase T
Por ejemplo:
T& T::operator=(T);
T& T::operator=(const T&);
Aquí, devuelve una referencia a T
, en lugar de simplemente a T
para hacer eficiente la asignación de tres partes como x = (y = z)
, sin requerir una copia.
Podría devolver una referencia const
a T
, lo que haría una asignación no deseada (a = b) = c
un error. Supongo que no usaron esto por dos razones:
- Código más corto: no es necesario que escriba todas estas condiciones todo el tiempo (los detalles
const
corrección deconst
no estaban claros en ese momento) - Más flexibilidad - permite código como
(a = b).print()
, dondeprint
es un método noconst
(porque el programador era flojo / ignorante deconst
-correctness)
La semántica para los tipos primitivos (que no son class
) se extrapolaron, para dar:
int& operator=(int&, int); // not real code; just a concept
El "tipo de devolución" no es const int&
por lo tanto coincide con el patrón con class
es. Por lo tanto, si el código buggy (a = b) = c
es válido para los tipos definidos por el usuario, también debería ser válido para los tipos incorporados, tal como lo requieren los principios de diseño de C ++ . Y una vez que documenta este tipo de cosas, no puede cambiarlo debido a la compatibilidad con versiones anteriores.
(a = b) = c
es una declaración válida en C ++. Aquí ''='' funciona como un operador de asignación. Aquí, el valor de b se asignará a a y el valor de c se asignará a una prioridad de derecha a izquierda.
Por ejemplo:
int a = 5;
int b = 2;
int c = 7;
int answer = (a = b) = c;
cout << answer << endl;
Salida:
7