todas - tipos de parametros en c++
¿La práctica de devolver una variable de referencia de C++ es mala? (16)
"devolver una referencia es malo porque, simplemente [como yo entiendo] hace que sea más fácil no borrarlo"
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 referida por v;
Sin embargo, este es un código horrible:
int& getTheValue()
{
return *new int;
}
Si está haciendo algo como esto porque "no necesita un puntero en esa instancia", entonces: 1) simplemente elimine la referencia al puntero si necesita una referencia, y 2) eventualmente necesitará el puntero, porque tiene que coincidir con un nuevo con una eliminación, y necesita un puntero para llamar a eliminar.
Esto es un poco subjetivo, creo; No estoy seguro de si la opinión será unánime (he visto muchos fragmentos de código donde se devuelven las referencias).
De acuerdo con un comentario a esta pregunta que acabo de hacer, en relación con la inicialización de referencias , devolver una referencia puede ser malo porque, [según entiendo] hace que sea más fácil omitirla, 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 ... ¿He entendido mal? Es malvado Si es así, ¿qué tan mal?
Siento que debido a mi conjunto mixto 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 un infierno de fugas de memoria ...
Además, entiendo que el uso de punteros inteligentes / compartidos es generalmente aceptado como la mejor manera de evitar pérdidas de memoria.
Creo que usar la referencia como el valor de retorno de la función es mucho más sencillo que usar el puntero como el valor de retorno de la función. En segundo lugar, siempre sería seguro utilizar una variable estática a la que se refiere el valor de retorno.
Debe devolver una referencia a un objeto existente que no se vaya a desaparecer de inmediato, y en el que no pretende ninguna transferencia de propiedad.
Nunca devuelva una referencia a una variable local o alguna, porque no estará allí para ser referenciada.
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 de la función típica del operator[]
.
Si está creando algo, debe devolver un valor o un puntero (normal 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á.
En general, devolver una referencia es perfectamente normal y sucede 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 se irá y usted no se refiere 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 eventualmente 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 de rvalue son solo referencias, por lo que todas las aplicaciones malvadas siguen siendo las mismas.
Si desea asignar algo que vive 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 cosas en las que sabes que la vida útil 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 nos esté llamando gestiona el tiempo de vida de la instancia de la clase, por lo que i_
vivirá al menos ese tiempo.
Y, por supuesto, no hay nada malo con solo:
int getInt() {
return 0;
}
Si la vida útil debe dejarse en manos de la persona que llama, y solo está calculando el valor.
Resumen: está bien devolver una referencia si la vida útil del objeto no termina después de la llamada.
Hay dos casos:
Referencia constante: buena idea, a veces, especialmente para objetos pesados o clases de proxy, optimización del compilador
Referencia no constante - idea errónea, a veces, rompe encapsulaciones
Ambos comparten el mismo problema: pueden apuntar a objetos destruidos ...
Recomendaría el uso de punteros inteligentes para muchas situaciones en las que necesite devolver una referencia / puntero.
Además, tenga en cuenta lo siguiente:
Existe una regla formal: el estándar de C ++ (sección 13.3.3.1.4 si está interesado) indica que un temporal solo puede vincularse a una referencia constante. Si intenta usar una referencia no constante, el compilador debe marcar esto como un error.
La función como lvalue (también conocido como retorno de referencias no constantes) debe eliminarse de C ++. Es terriblemente poco intuitivo. Scott Meyers quería un min () con este comportamiento.
min(a,b) = 0; // What???
que no es realmente una mejora en
setmin (a, b, 0);
Este último incluso tiene más sentido.
Me doy cuenta de que la función como valor l es importante para las secuencias de estilo C ++, pero vale la pena señalar que las corrientes de estilo C ++ son terribles. No soy el único que piensa esto ... como recuerdo, Alexandrescu tenía un gran artículo sobre cómo hacerlo mejor, y creo que boost también ha intentado crear un método de E / S seguro de tipo mejor.
Las respuestas no me parecen satisfactorias, así que agregaré mis dos centavos.
Analicemos los siguientes casos:
Uso erróneo
int& getInt()
{
int x = 4;
return x;
}
Esto es obviamente 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"
Los operadores
Los contenedores de bibliotecas estándar dependen en gran medida del uso de operadores que devuelven referencias, por ejemplo
T & operator*();
puede ser utilizado en los siguientes
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 los datos internos.
Hay momentos en que se puede usar para acceder rápidamente a los 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 llevar a escollos como este:
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!
Lo mejor es crear un objeto y pasarlo como parámetro de referencia / puntero a una función que asigna esta variable.
Asignar un objeto en función y devolverlo como una referencia o un puntero (el puntero es más seguro, sin embargo) es una mala idea, ya que libera memoria al final del bloque de funciones.
Me encontré con un problema real donde era realmente malo. Esencialmente, un desarrollador devolvió una referencia a un objeto en un vector. ¡¡¡Eso fue malo!!!
Los detalles completos sobre los que escribí en enero: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html
No es malvado Como muchas cosas en C ++, es bueno si se usa correctamente, pero hay muchos escollos que debe tener en cuenta al usarlo (como devolver una referencia a una variable local).
Hay cosas buenas que se pueden lograr con él (como map [name] = "hello world")
No solo no es el mal, a veces es esencial. Por ejemplo, sería imposible implementar el operador [] de std :: vector sin utilizar un valor de retorno de referencia.
No. No, no, mil veces no.
Lo que es malo es hacer referencia a un objeto asignado dinámicamente y perder el puntero original. Cuando new
un new
objeto, asume la obligación de tener una delete
garantizada.
Pero observe, por ejemplo, el operator<<
: que debe devolver una referencia, o
cout << "foo" << "bar" << "bletch" << endl ;
no funcionará
Sobre código horrible:
int& getTheValue()
{
return *new int;
}
Así que, efectivamente, puntero de memoria perdido después de volver. Pero si usas shared_ptr así:
int& getTheValue()
{
std::shared_ptr<int> p(new int);
return *p->get();
}
La memoria no se pierde después del retorno y se liberará después de la asignación.
la referencia de retorno generalmente se usa en la sobrecarga de operadores en C ++ para Objetos grandes, porque devolver un valor necesita una operación de copia (en la sobrecarga de peradores, generalmente no usamos el puntero como valor de retorno)
Pero la referencia de retorno puede causar problemas de asignación de memoria. Debido a que una referencia al resultado se pasará de la función como una referencia al valor de retorno, el valor de retorno no puede ser una variable automática.
Si desea usar la referencia de retorno, 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, podría utilizar la referencia de retorno de forma segura.
Pero siempre se puede usar un puntero en lugar de una referencia para devolver el valor en functiong.
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 no está bien y debería evitarse si es posible. ¿Por qué? Es muy fácil terminar con una referencia colgando .
Para ilustrar el punto con un ejemplo:
struct Foo
{
Foo(int i = 42) : boo_(i) {}
immutableint boo()
{
return boo_;
}
private:
immutableint boo_;
};
entrando en la zona de peligro:
Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!
Class Set {
int *ptr;
int size;
public:
Set(){
size =0;
}
Set(int size) {
this->size = size;
ptr = new int [size];
}
int& getPtr(int i) {
return ptr[i]; // bad practice
}
};
La función getPtr puede acceder a la memoria dinámica después de la eliminación o incluso a un objeto nulo. Lo que puede causar malas excepciones de acceso. En su lugar, se debe implementar el getter y el setter y se debe verificar el tamaño antes de regresar.