2009-06-15 20 views
9

Así que yo estaba escribiendo algo de código, y yo tenía algo como esto:¿Hay una forma correcta de devolver una nueva instancia de objeto por referencia en C++?

class Box 
{ 
    private: 
    float x, y, w, h; 

    public: 
    //... 
    Rectangle & GetRect(void) const 
    { 
     return Rectangle(x, y, w, h); 
    } 
}; 

Luego, más tarde en algún código:

Rectangle rect = theBox.GetRect(); 

que funcionó en mi generación de depuración, pero en la versión que había "cuestiones "devolviendo ese rectángulo por referencia, básicamente obtuve un rectángulo no inicializado. La clase Rectangle tiene un operador = y un constructor de copia. Sin entrar en el por qué esto se rompió, estoy realmente más interesado en la forma correcta de devolver un objeto (nuevo) por referencia con el propósito de asignando copiando a una variable. ¿Solo estoy siendo tonto? ¿No debería hacerse? Sé que puedo devolver un puntero y luego eliminar la referencia en la asignación, pero prefiero no hacerlo. Alguna parte de mí siente que regresar por valor resultaría en una copia redundante del objeto. ¿Lo descifra el compilador y lo optimiza?

Parece una pregunta trivial. Me siento casi avergonzado. No lo sé después de muchos años de codificación C++, así que espero que alguien pueda aclararme esto. :)

+0

Tenga en cuenta que no hay una asignación en el código. –

Respuesta

21

No puede devolver una referencia a un objeto temporal en la pila. Tiene tres opciones:

  1. enviarlo por valor
  2. retorno por referencia a través de un puntero a algo que ha creado en el montón con el nuevo operador.
  3. Devuelva por referencia lo que recibió por referencia como argumento. [EDIT: Gracias a @ harshath.jr por señalar esto]

Tenga en cuenta que cuando regrese al valor que en el código siguiente, el compilador debe optimizar la asignación de evitar la copia - es decir, que se acaba de crear una Rectángulo simple (rect) optimizando la creación + asignación + copia en una creación. Esto solo funciona cuando crea el nuevo objeto cuando regresa de la función.

Rectangle GetRect(void) const 
{ 
    return Rectangle(x, y, w, h); 
} 

Rectangle rect = theBox.GetRect(); 
+5

Una vez más, no hay asignación en este código. –

+3

Opción 3: devuelva por referencia lo que recibió como argumento por referencia.Esto se usa especialmente al anular operadores. – jrharshath

+0

ejemplos de puntero habrían sido bueno en esta respuesta como rectángulo * getRect (void) {const devolver nuevo rectángulo (x, y, w, h) } rectángulo rect * = theBox.GetRect() ; posterior borrar rect; – AppDeveloper

15

No, no puedes hacer esto. Esencialmente, lo que intenta hacer en esta muestra es devolver una referencia a una variable temporal en la pila. Para cuando se devuelve la referencia, la variable a la que apunta se destruirá y, por lo tanto, la referencia no es válida.

+0

¿Estás diciendo que si fuera const Rectangle & GetRect (...) sería correcto? –

+0

El problema no es la constness sino más bien que lo que tiene una referencia se crea en la pila. –

+0

@pbhogan, no. No debería haber agregado la palabra const a mi respuesta. Ese fue un error de mi parte. Deberías devolver esto por valor aquí. – JaredPar

2
  • De cualquier devolver una referencia a las entrañas de la clase Box (tener un miembro de Rectangle. Volviendo se aconseja una referencia const).
  • o simplemente devuelva un Rectangle. Tenga en cuenta que el uso de la expresión idiomática return SomeClass(a,b,c); probablemente desencadenará un return value optimization (RVO) en un compilador decente.

Compruebe su implementación std::complex para más detalles.

2

Puede estar confundido por el concepto de la vida útil de un temporal. Considere:

void f1(const A & a) { 
} 

A f2() { 
    return A; 
} 

f1(f2()); 

Este es el código OK, y la norma dice que los sin nombre temporal que crea f2 debe colgar alrededor el tiempo suficiente para ser utilizable en la F1.

Sin embargo, su caso es algo diferente. Lo que devuelve tu función es una referencia y, por lo tanto, el temporal sin nombre también es una referencia. Esa referencia debe permanecer el tiempo suficiente como para ser útil, pero no necesariamente lo que se refiere a lo que se refiere.

+0

Tienes razón, por supuesto. Estuve pasando demasiado tiempo en lenguajes dinámicos: comencé a esperar que la vida del temporal siguiera la referencia. –

1

Esto no es posible. Una referencia es otra forma de un puntero y de hecho se devuelve una dirección de un objeto que se habrá destruido (llamado por el destructor) y posiblemente incluso se sobrescribirá cuando la persona que llama reciba el control.

Puede

  • llamada de nuevo y devolver un puntero (tal vez debería pensar en un puntero inteligente) a un objeto heap-asignados o
  • retorno por valor o
  • pasar el objeto por referencia en una función para que lo llene.
7

Devolver un objeto por valor (ver el ejemplo a continuación) puede ser menos costoso de lo que crees. El compilador a menudo optimiza la copia extra. Esto se llama return value optimization.

Rectangle GetRect(void) const 
    { 
      return Rectangle(x, y, w, h); 
    } 
+0

+1 para nombrar la optimización específica permitida –

0

Si el bit a bit rectángulo se parece a la caja es decir, se compone de cuatro flotadores (pero tiene diferentes funciones miembro) se puede utilizar un reinterpret_cast, aunque yo no en absoluto recomiendo:

const Rectangle & GetRect(void) const 
    { 
      assert(sizeof(Rectangle) == sizeof(Box)); 
      return reinterpret_cast <Rectangle> (*this); 
    } 
0

podemos utilizar auto_ptr, si queremos utilizar la nueva y salvo de pérdida de memoria

class Box { 

    private: float x, y, w, h; 

    public:  

    //...  

    std::auto_ptr<Rectangle> GetRect(void) const 
    {   
     return std::auto_ptr<Rectangle> (new Rectangle(x, y, w, h)); 
    } 

}; 
+0

use shared_ptr hoy en día. – gbjbaanb

2

¿Hay una forma correcta de devolver una nueva instancia de objeto por referencia en C++?

No, no por referencia. Hay dos maneras de crear un nuevo objeto:

en la pila:

Rectangle makeRect() 
{ 
    return Rectangle(x, y, w, h); 
} 
Rectangle r = makeRect(); // return by value 

en el montón:

Rectangle * makeRect() 
{ 
    return new Rectangle(x, y, w, y); 
} 
Rectangle * r = makeRect(); // returned a pointer, don't forget to delete it later 

Por qué no algo como esto?

class Box 
{ 
    private: 
    Rectangle mRectangle; 

    public: 
    Box(float x, float y, float w, float h) : 
     mRectangle(x, y, w, h) // Forgive me for making assumptions 
          // about the inner workings of your 
          // code here. 
    { 
    } 

    const Rectangle & GetRect() const 
    { 
     return mRectangle; 
    } 
}; 

Rectangle rect = theBox.GetRect(); 

La 'asignación' debería funcionar ahora. (Técnicamente esto no es un operador de asignación, sino un constructor de copia se invoca.)

Con la esperanza de ayudar a

+0

Un buen resumen de mis opciones, gracias. Implemento su última opción siempre que sea posible, sin embargo, mi ejemplo aquí fue algo simplista. Box es en realidad algo más complicado y GetRect devuelve un rectángulo calculado. De todos modos, tu primera opción es lo que debería estar haciendo ... Estaba intentando ser más inteligente que el compilador, supongo: p –

Cuestiones relacionadas