Pregunta ¿La práctica de devolver una variable de referencia de C ++ es mala?


Esto es un poco subjetivo, creo; No estoy seguro de si la opinión será unánime (he visto una gran cantidad de fragmentos de código donde se devuelven las referencias).

De acuerdo con un comentario hacia esta pregunta que acabo de hacer, con respecto a la inicialización de referencias, devolver una referencia puede ser malo porque, [según tengo entendido], hace que sea más fácil pasar por alto eliminarlo, lo que puede provocar pérdidas de memoria.

Esto me preocupa, ya que he seguido ejemplos (a menos que esté imaginando cosas) y he hecho esto en unos pocos lugares ... ¿Lo he entendido mal? ¿Es malvado? Si es así, ¿qué tan malvado?

Siento que debido a mi mezcla de punteros y referencias, combinado con el hecho de que soy nuevo en C ++, y la confusión total sobre qué usar cuando, mis aplicaciones deben ser fuga de memoria infernal ...

Además, entiendo que el uso de punteros inteligentes / compartidos generalmente se acepta como la mejor manera de evitar fugas de memoria.


282


origen


Respuestas:


En general, devolver una referencia es perfectamente normal y ocurre todo el tiempo.

Si te refieres a:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

Eso es todo tipo de maldad. La pila asignada i desaparecerá y te estás refiriendo a nada. Esto también es malo:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Porque ahora el cliente tiene que hacer lo extraño:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil

int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Tenga en cuenta que las referencias rvalue siguen siendo solo referencias, por lo que todas las aplicaciones malvadas siguen siendo las mismas.

Si desea asignar algo que está más allá del alcance de la función, use un puntero inteligente (o, en general, un contenedor):

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

Y ahora el cliente almacena un puntero inteligente:

std::unique_ptr<int> x = getInt();

Las referencias también están bien para acceder a las cosas donde usted sabe que la vida se mantiene abierta en un nivel superior, por ejemplo:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Aquí sabemos que está bien devolver una referencia a i_ porque lo que sea que nos llame maneja la vida de la instancia de clase, por lo i_ vivirá al menos ese tiempo.

Y, por supuesto, no hay nada malo con solo:

int getInt() {
   return 0;
}

Si la vida útil se debe dejar a la persona que llama, y ​​usted solo está calculando el valor.

Resumen: está bien devolver una referencia si la duración del objeto no finalizará después de la llamada.


334



No. No, no, mil veces no.

Lo que es malo es hacer referencia a un objeto dinámicamente asignado y perder el puntero original. Cuando tú new un objeto que asume la obligación de tener un garantizado delete.

Pero eche un vistazo a, por ejemplo, operator<<: ese debe devolver una referencia, o

cout << "foo" << "bar" << "bletch" << endl ;

no funcionará


58



Debería devolver una referencia a un objeto existente que no se va a eliminar de inmediato, y en el que no tiene intención de transferir la propiedad.

Nunca devuelva una referencia a una variable local o algo así, porque no estará allí para hacer referencia.

Puede devolver una referencia a algo independiente de la función, que no espera que la función de llamada asuma la responsabilidad de eliminar. Este es el caso para el típico operator[] función.

Si está creando algo, debe devolver un valor o un puntero (regular o inteligente). Puede devolver un valor libremente, ya que entra en una variable o expresión en la función de llamada. Nunca devuelva un puntero a una variable local, ya que desaparecerá.


41



No es malvado Al igual que muchas cosas en C ++, es bueno si se usa correctamente, pero hay muchas trampas que debe tener en cuenta al usarla (como devolver una referencia a una variable local).

Hay cosas buenas que se pueden lograr con él (como map [name] = "hello world")


14



Las respuestas no son satisfactorias, así que agregaré mis dos centavos.

Analicemos los siguientes casos:

Uso erróneo

int& getInt()
{
    int x = 4;
    return x;
}

Esto es obviamente un error

int& x = getInt(); // will refer to garbage

Uso con variables estáticas

int& getInt()
{
   static int x = 4;
   return x;
}

Esto es correcto, porque las variables estáticas existen durante toda la vida útil de un programa.

int& x = getInt(); // valid reference, x = 4

Esto también es bastante común cuando se implementa el patrón Singleton

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

Uso:

 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  // "Hello"

Operadores

Los contenedores de biblioteca estándar dependen en gran medida del uso de operadores que devuelven referencia, por ejemplo

T & operator*();

puede ser utilizado en el siguiente

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

Acceso rápido a datos internos

Hay momentos en los que & puede usarse para acceder rápidamente a datos internos

Class Container
{
    private:
        std::vector<int> m_data;

    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

con uso:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

SIN EMBARGO, esto puede conducir a una trampa como esta:

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!

12



"devolver una referencia es malo porque,   simplemente [como yo lo entiendo] lo hace   más fácil pasar por alto eliminarlo "

No es verdad. Devolver una referencia no implica semántica de propiedad. Es decir, solo porque haces esto:

Value& v = thing->getTheValue();

... no significa que ahora poseas la memoria mencionada por v;

Sin embargo, este es un código horrible:

int& getTheValue()
{
   return *new int;
}

Si estás haciendo algo como esto porque "no necesitas un puntero en esa instancia" luego: 1) simplemente desreferencia el puntero si necesita una referencia, y 2) eventualmente necesitará el puntero, porque tiene que hacer coincidir un nuevo con un eliminar, y necesita un puntero para llamar a eliminar.


10



Hay dos casos:

  • referencia de referencia - buena idea, a veces, especialmente para objetos pesados ​​o clases proxy, optimización del compilador

  • referencia no const .: mala idea, a veces, rompe encapsulaciones

Ambos comparten el mismo problema: pueden apuntar al objeto destruido ...

Recomendaría utilizar punteros inteligentes para muchas situaciones en las que necesita devolver una referencia / puntero.

Además, tenga en cuenta lo siguiente:

Existe una regla formal: el Estándar C ++ (sección 13.3.3.1.4 si está interesado) establece que un temporal solo puede vincularse a una referencia constante: si intenta utilizar una referencia no constante, el compilador debe marcar esto como un error.


7



No solo no es malo, a veces es esencial. Por ejemplo, sería imposible implementar el operador [] de std :: vector sin usar un valor de retorno de referencia.


4



La referencia de retorno se usa generalmente en la sobrecarga del operador en C ++ para objetos grandes, porque es necesario devolver el valor de la operación de copia (en la sobrecarga de perator, normalmente no usamos el puntero como valor de retorno)

Pero la referencia de devolución puede causar problemas de asignación de memoria. Debido a que se pasará una referencia al resultado de la función como una referencia al valor de retorno, el valor de retorno no puede ser una variable automática.

si desea utilizar el retorno de referencia, puede usar un búfer de objeto estático. por ejemplo

const max_tmp=5; 
Obj& get_tmp()
{
 static int buf=0;
 static Obj Buf[max_tmp];
  if(buf==max_tmp) buf=0;
  return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
 Obj& res=get_tmp();
 // +operation
  return res;
 }

de esta manera, puede usar la referencia de retorno de forma segura.

Pero siempre se puede usar puntero en lugar de referencia para devolver el valor en functiong.


1



Además de la respuesta aceptada:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Yo diría que este ejemplo es no esta bien y debe evitarse si es posible. ¿Por qué? Es muy fácil terminar con un referencia colgante.

Para ilustrar el punto con un ejemplo:

struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

entrando a la zona de peligro:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!

1



Creo que el uso de referencia como el valor de retorno de la función es mucho más directo que el uso de puntero como el valor de retorno de la función. En segundo lugar, siempre sería seguro usar la variable estática a la que se refiere el valor de retorno.


0