Está capturando un objeto recién construido por const ref comportamiento indefinido

11

¿Está bien el siguiente (ejemplo artificial) o es un comportamiento indefinido:

// undefined behavior?
const auto& c = SomeClass{};

// use c in code later
const auto& v = c.GetSomeVariable();
Samaursa
fuente

Respuestas:

12

Es seguro. Const ref prolonga la vida de temporal. El alcance será el alcance de const ref.

La vida útil de un objeto temporal puede extenderse uniéndose a una referencia de valor constante o a una referencia de valor (desde C ++ 11), consulte la inicialización de referencia para obtener más detalles.

Cada vez que una referencia está vinculada a un objeto temporal o subobjeto del mismo, la vida útil del temporal se extiende para que coincida con la vida útil de la referencia, con las siguientes excepciones :

  • un enlace temporal a un valor de retorno de una función en una declaración de retorno no se extiende: se destruye inmediatamente al final de la expresión de retorno. Dicha función siempre devuelve una referencia colgante.
  • un enlace temporal a un miembro de referencia en una lista de inicializador de constructor persiste solo hasta que el constructor sale, no mientras exista el objeto. (nota: dicha inicialización está mal formada a partir del DR 1696).
  • existe un enlace temporal a un parámetro de referencia en una llamada de función hasta el final de la expresión completa que contiene esa llamada de función: si la función devuelve una referencia, que sobrevive a la expresión completa, se convierte en una referencia colgante.
  • existe un enlace temporal a una referencia en el inicializador utilizado en una nueva expresión hasta el final de la expresión completa que contiene esa nueva expresión, no tanto como el objeto inicializado. Si el objeto inicializado sobrevive a la expresión completa, su miembro de referencia se convierte en una referencia colgante.
  • existe un enlace temporal a una referencia en un elemento de referencia de un agregado inicializado utilizando la sintaxis de inicialización directa (paréntesis) en oposición a la sintaxis de inicialización de lista (llaves) hasta el final de la expresión completa que contiene el inicializador. struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference

En general, la vida útil de un temporal no puede extenderse aún más "pasándola": una segunda referencia, inicializada a partir de la referencia a la que estaba vinculado el temporal, no afecta su vida útil.

como señaló @Konrad Rudolph (y vea el último párrafo de arriba):

"Si c.GetSomeVariable()devuelve una referencia a un objeto local o una referencia de que está extendiendo la vida útil de algún objeto, la extensión de la vida útil no se activa"

Olvido
fuente
1
Deberías citar la fuente de esa cita.
Carreras ligeras en órbita el
@LightnessRaceswithMonica hecho. Estaba buscando un texto mejor.
Olvido
2
Sería bueno subrayar que esto solo es cierto para los valores . Si c.GetSomeVariable()devuelve una referencia a un objeto local o una referencia de que está extendiendo la vida útil de algún objeto, la extensión de la vida útil no se activa.
Konrad Rudolph el
@KonradRudolph ¡Gracias! También agregué la excepción.
Olvido
4

No debería haber ningún problema aquí, gracias a la extensión de por vida . El objeto recién construido sobrevivirá hasta que la referencia salga del alcance.

Brian
fuente
3

Sí, esto es perfectamente seguro: el enlace a una constreferencia extiende la vida útil de lo temporal al alcance de esa referencia.

Sin embargo, tenga en cuenta que el comportamiento no es transitivo . Por ejemplo, con

const auto& cc = []{
    const auto& c = SomeClass{};
    return c;
}();

cc cuelga

Betsabé
fuente
2

Esto es seguro

[class.temporary]/5: Hay tres contextos en los que los temporales se destruyen en un punto diferente al final de la expresión completa . [..]

[class.temporary]/6: El tercer contexto es cuando una referencia está vinculada a un objeto temporal. El objeto temporal al que está vinculada la referencia o el objeto temporal que es el objeto completo de un subobjeto al que está vinculada la referencia persiste durante toda la vida útil de la referencia si el valor gl al que está vinculada la referencia se obtuvo a través de uno de los siguientes : [muchas cosas aquí]

Carreras de ligereza en órbita
fuente
1

Es seguro en este caso específico. Sin embargo, tenga en cuenta que no todos los temporales son seguros de capturar por referencia constante ... por ejemplo

#include <stdio.h>

struct Foo {
    int member;

    Foo() : member(0) {
        printf("Constructor\n");
    }

    ~Foo() {
        printf("Destructor\n");
    }

    const Foo& method() const {
        return *this;
    }
};

int main() {
    {
        const Foo& x = Foo{};        // safe
        printf("here!\n");
    }
    {
        const int& y = Foo{}.member; // safe too (special rule for this)
        printf("here (2)!\n");
    }
    {
        const Foo& z = Foo{}.method(); // NOT safe
        printf("here (3)!\n");
    }
    return 0;
}

La referencia obtenida para zNO es segura de usar porque la instancia temporal se destruirá al final de la expresión completa, antes de llegar a la printfdeclaración. Salida es:

Constructor
here!
Destructor
Constructor
here (2)!
Destructor
Constructor
Destructor
here (3)!
6502
fuente