¿Por qué no está permitido obtener referencias no constantes a un objeto temporal, que función getx()
devuelve? Claramente, esto está prohibido por C ++ Standard pero estoy interesado en el propósito de dicha restricción, no en una referencia al estándar.
struct X
{
X& ref() { return *this; }
};
X getx() { return X();}
void g(X & x) {}
int f()
{
const X& x = getx(); // OK
X& x = getx(); // error
X& x = getx().ref(); // OK
g(getx()); //error
g(getx().ref()); //OK
return 0;
}
- Está claro que la vida útil del objeto no puede ser la causa, porque C ++ Standard no prohíbe la referencia constante a un objeto .
- Está claro que el objeto temporal no es constante en la muestra anterior, porque se permiten llamadas a funciones no constantes. Por ejemplo,
ref()
podría modificar el objeto temporal. - Además, le
ref()
permite engañar al compilador y obtener un enlace a este objeto temporal y eso resuelve nuestro problema.
Adicionalmente:
Dicen que "asignar un objeto temporal a la referencia constante extiende la vida útil de este objeto" y "Sin embargo, no se dice nada sobre las referencias no constantes". Mi pregunta adicional . ¿La siguiente asignación extiende la vida útil del objeto temporal?
X& x = getx().ref(); // OK
Respuestas:
De este artículo del blog de Visual C ++ sobre las referencias de rvalue :
Básicamente, no deberías intentar modificar los temporales por el solo hecho de que son objetos temporales y morirán en cualquier momento. La razón por la que puede llamar a métodos no constantes es que, bueno, puede hacer algunas cosas "estúpidas" siempre y cuando sepa lo que está haciendo y sea explícito al respecto (por ejemplo, usando reinterpret_cast). Pero si vincula una referencia temporal a una referencia no constante, puede seguir pasándola "para siempre" solo para que su manipulación del objeto desaparezca, porque en algún momento del camino olvidó por completo que esto era temporal.
Si yo fuera usted, repensaría el diseño de mis funciones. ¿Por qué g () acepta referencia, modifica el parámetro? Si no, haga referencia constante, en caso afirmativo, ¿por qué intenta pasarle temporalmente? ¿No le importa que sea una modificación temporal? ¿Por qué getx () regresa temporalmente de todos modos? Si comparte con nosotros su escenario real y lo que está tratando de lograr, puede obtener algunas buenas sugerencias sobre cómo hacerlo.
Ir en contra del lenguaje y engañar al compilador rara vez resuelve problemas, por lo general crea problemas.
Editar: Abordar preguntas en el comentario: 1)
X& x = getx().ref(); // OK when will x die?
- No lo sé y no me importa, porque esto es exactamente lo que quiero decir con "ir en contra del idioma". El lenguaje dice que "los temporales mueren al final de la declaración, a menos que estén obligados a hacer referencia constante, en cuyo caso mueren cuando la referencia queda fuera de alcance". Aplicando esa regla, parece que x ya está muerto al comienzo de la siguiente declaración, ya que no está obligado a const referencia (el compilador no sabe qué devuelve () devuelve). Sin embargo, esto es solo una suposición.2) Indiqué claramente el propósito: no está permitido modificar temporarios, porque simplemente no tiene sentido (ignorando las referencias de valor de C ++ 0x). La pregunta "entonces, ¿por qué se me permite llamar a miembros que no son constantes?" es buena, pero no tengo una mejor respuesta que la que dije anteriormente.
3) Bueno, si estoy en lo cierto al
X& x = getx().ref();
morir x al final de la declaración, los problemas son obvios.De todos modos, según su pregunta y comentarios, no creo que incluso estas respuestas adicionales lo satisfagan. Aquí hay un último intento / resumen: el comité de C ++ decidió que no tiene sentido modificar los temporales, por lo tanto, rechazaron la vinculación a referencias no constantes. Puede haber alguna implementación del compilador o también hubo problemas históricos, no lo sé. Luego, surgió un caso específico, y se decidió que, contra todo pronóstico, todavía permitirán la modificación directa mediante la llamada al método no constante. Pero eso es una excepción: generalmente no se le permite modificar los temporales. Sí, C ++ es a menudo así de raro.
fuente
int
etc.), pero se permite para los tipos definidos por el usuario:(std::string("A")+"B").append("C")
.g()
modifica el objeto (lo que cabría esperar de una función que toma una referencia no constante), modificaría un objeto que va a morir, para que nadie pueda obtener el valor modificado de todos modos. Él dice que esto, muy probablemente, es un error.En su código
getx()
devuelve un objeto temporal, un llamado "rvalue". Puede copiar valores en objetos (también conocidos como variables) o vincularlos a referencias constantes (lo que extenderá su vida útil hasta el final de la vida de la referencia). No puede vincular valores a referencias no constantes.Esta fue una decisión de diseño deliberada para evitar que los usuarios modifiquen accidentalmente un objeto que va a morir al final de la expresión:
Si desea hacer esto, primero deberá hacer una copia local o del objeto o vincularlo a una referencia constante:
Tenga en cuenta que el próximo estándar C ++ incluirá referencias rvalue. Por lo tanto, lo que conoce como referencias se está convirtiendo en "referencias de valor". Se le permitirá vincular rvalues a referencias de rvalue y puede sobrecargar funciones en "rvalue-ness":
La idea detrás de las referencias de valor es que, dado que estos objetos van a morir de todos modos, puede aprovechar ese conocimiento e implementar lo que se llama "semántica de movimiento", un cierto tipo de optimización:
fuente
g(getx())
no funciona porque su firma esg(X& x)
yget(x)
devuelve un objeto temporal, por lo que no podemos vincular un objeto temporal ( rvalue ) a una referencia no constante, ¿correcto? Y en su primer código, creo que será enconst X& x2 = getx();
lugar deconst X& x1 = getx();
...:-/
Sí, su razonamiento es correcto, aunque un poco al revés: no podemos vincular temporales aconst
referencias que no sean (lvalue), y por lo tanto, el temporal devuelto porgetx()
(y noget(x)
) no puede vincularse a la referencia de lvalue a la que se refiere el argumentog()
.getx()
(y noget(x)
) ?getx()
, y noget(x)
(como usted escribió).Lo que está mostrando es que se permite el encadenamiento del operador.
La expresión es 'getx (). Ref ();' y esto se ejecuta hasta su finalización antes de la asignación a 'x'.
Tenga en cuenta que getx () no devuelve una referencia sino un objeto completamente formado en el contexto local. El objeto es temporal, pero es no constante, lo que le permite llamar a otros métodos para calcular un valor o tener otros efectos secundarios ocurren.
Mire al final de esta respuesta para un mejor ejemplo de esto
Puede que no se unen a un temporal a una referencia ya que al hacerlo va a generar una referencia a un objeto que será destruido al final de la expresión que lo que deja colgando con una referencia (que es desordenado y el estándar no le gusta desordenado).
El valor devuelto por ref () es una referencia válida, pero el método no presta atención a la vida útil del objeto que está devolviendo (porque no puede tener esa información dentro de su contexto). Básicamente has hecho el equivalente de:
La razón por la que está bien hacer esto con una referencia constante a un objeto temporal es que el estándar extiende la vida útil de lo temporal a la vida útil de la referencia, por lo que la vida útil de los objetos temporales se extiende más allá del final de la declaración.
Entonces, la única pregunta que queda es ¿por qué el estándar no quiere permitir referencias a temporales para extender la vida del objeto más allá del final de la declaración?
Creo que es porque hacerlo haría que el compilador sea muy difícil de corregir para objetos temporales. Se realizó para referencias constantes a los temporales, ya que esto tiene un uso limitado y, por lo tanto, lo obligó a hacer una copia del objeto para hacer algo útil, pero proporciona alguna funcionalidad limitada.
Piensa en esta situación:
Extender la vida útil de este objeto temporal será muy confuso.
Mientras que lo siguiente:
Le dará un código que es intuitivo de usar y comprender.
Si desea modificar el valor, debe devolver el valor a una variable. Si está tratando de evitar el costo de copiar el objeto desde la función (ya que parece que el objeto es una copia reconstruida (técnicamente lo es)). Entonces no te molestes, el compilador es muy bueno en 'Optimización del valor de retorno'
fuente
const_cast<x&>(getX());
no tiene sentido¿Por qué se discute en las preguntas frecuentes de C ++ ( negrita mía):
fuente
x * 0
dax
? ¿Qué? ¿¿Qué??incr(0);
de esa manera. Obviamente, si esto se permitiera, se interpretaría como la creación de un número entero temporal y pasarlo aincr()
El problema principal es que
es un error lógico:
g
está modificando el resultadogetx()
pero no tiene ninguna posibilidad de examinar el objeto modificado. Sig
no fuera necesario modificar su parámetro, entonces no habría requerido una referencia de valor, podría haber tomado el parámetro por valor o por referencia constante.es válido porque a veces necesita reutilizar el resultado de una expresión, y está bastante claro que está tratando con un objeto temporal.
Sin embargo, no es posible hacer
válido sin hacerlo
g(getx())
válido, que es lo que los diseñadores de idiomas intentaban evitar en primer lugar.es válido porque los métodos solo saben acerca de la consistencia del
this
, no saben si se les llama en un valor o en un valor.Como siempre en C ++, tiene una solución para esta regla, pero debe indicarle al compilador que sabe lo que está haciendo al ser explícito:
fuente
Parece que la pregunta original de por qué esto no está permitido se ha respondido claramente: "porque es muy probable que sea un error".
FWIW, pensé que mostraría cómo hacerlo, aunque no creo que sea una buena técnica.
La razón por la que a veces quiero pasar un método temporal a un método que toma una referencia no constante es descartar intencionalmente un valor devuelto por referencia que al método de llamada no le importa. Algo como esto:
Como se explicó en las respuestas anteriores, eso no se compila. Pero esto compila y funciona correctamente (con mi compilador):
Esto solo muestra que puedes usar el casting para mentirle al compilador. Obviamente, sería mucho más limpio declarar y pasar una variable automática no utilizada:
Esta técnica introduce una variable local innecesaria en el alcance del método. Si por alguna razón desea evitar que se use más adelante en el método, por ejemplo, para evitar confusiones o errores, puede ocultarlo en un bloque local:
- Chris
fuente
¿Por qué querrías alguna vez
X& x = getx();
? Solo useX x = getx();
y confíe en RVO.fuente
g(getx())
lugar deg(getx().ref())
g
va a modificar algo que ya no puedes tener en tus manos.La solución malvada implica la palabra clave 'mutable'. En realidad, ser malvado se deja como un ejercicio para el lector. O vea aquí: http://www.ddj.com/cpp/184403758
fuente
Excelente pregunta, y aquí está mi intento de una respuesta más concisa (dado que hay mucha información útil en los comentarios y difícil de excavar en el ruido).
Cualquier referencia vinculada directamente a un temporal extenderá su vida [12.2.5]. Por otro lado, una referencia inicializada con otra referencia no lo hará (incluso si finalmente es el mismo temporal). Eso tiene sentido (el compilador no sabe a qué se refiere esa referencia en última instancia).
Pero toda esta idea es extremadamente confusa. Por ejemplo,
const X &x = X();
hará que lo temporal dure tanto como lax
referencia, peroconst X &x = X().ref();
NO lo hará (quién sabe lo queref()
realmente regresó). En el último caso, el destructor paraX
se llama al final de esta línea. (Esto es observable con un destructor no trivial).Por lo tanto, en general parece confuso y peligroso (¿por qué complicar las reglas sobre la vida útil de los objetos?), Pero presumiblemente era necesario al menos referencias constantes, por lo que el estándar establece este comportamiento para ellos.
Todos los temporales persisten hasta el final de la expresión completa. Sin embargo, para utilizarlos, necesitas un truco como el que tienes
ref()
. Eso es legal No parece haber una buena razón para que salte el aro adicional, excepto para recordarle al programador que está sucediendo algo inusual (es decir, un parámetro de referencia cuyas modificaciones se perderán rápidamente).fuente
"Está claro que el objeto temporal no es constante en la muestra anterior, porque se permiten llamadas a funciones no constantes. Por ejemplo, ref () podría modificar el objeto temporal".
En su ejemplo, getX () no devuelve una constante X, por lo que puede llamar a ref () de la misma manera que podría llamar a X (). Ref (). Está devolviendo una referencia no constante y, por lo tanto, puede llamar a métodos no constantes, lo que no puede hacer es asignar la referencia a una referencia no constante.
Junto con el comentario de SadSidos, esto hace que sus tres puntos sean incorrectos.
fuente
Tengo un escenario que me gustaría compartir donde desearía poder hacer lo que Alexey me pide. En un complemento Maya C ++, tengo que hacer el siguiente shenanigan para obtener un valor en un atributo de nodo:
Esto es tedioso de escribir, así que escribí las siguientes funciones auxiliares:
Y ahora puedo escribir el código desde el principio de la publicación como:
El problema es que no se compila fuera de Visual C ++, porque está tratando de vincular la variable temporal devuelta por | operador a la referencia MPlug del operador <<. Me gustaría que fuera una referencia porque este código se llama muchas veces y prefiero que MPlug no se copie tanto. Solo necesito el objeto temporal para vivir hasta el final de la segunda función.
Bueno, este es mi escenario. Solo pensé en mostrar un ejemplo en el que a uno le gustaría hacer lo que Alexey describe. Agradezco todas las críticas y sugerencias!
Gracias.
fuente