A prática de retornar uma variável de referência C ++ é maléfica?

Isso é um pouco subjetivo, eu acho; Eu não tenho certeza se a opinião será unânime (eu vi um monte de trechos de código onde as referências são retornadas).

De acordo com um comentário em relação a essa pergunta, perguntei, em relação à boot de referências , retornar uma referência pode ser ruim porque, como eu entendo, fica mais fácil deixar de excluí-la, o que pode levar a vazamentos de memory.

Isso me preocupa, pois eu segui exemplos (a menos que eu esteja imaginando coisas) e fiz isso em poucos lugares … Eu entendi errado? Isso é mal? Se sim, o quão mal?

Eu sinto que por causa da minha mescla de pointers e referências, combinado com o fato de que eu sou novo em C ++, e total confusão sobre o que usar quando, meus aplicativos devem ser um memory leaks infernal …

Além disso, eu entendo que o uso de pointers inteligentes / compartilhados é geralmente aceito como a melhor maneira de evitar vazamentos de memory.

Em geral, retornar uma referência é perfeitamente normal e acontece o tempo todo.

Se você diz:

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

Isso é todo tipo de mal. A alocação de pilha i vou embora e você não está se referindo a nada. Isso também é mal:

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

Porque agora o cliente tem que eventualmente fazer o estranho:

 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 

Note que as referências de valor ainda são apenas referências, então todas as aplicações malignas permanecem as mesmas.

Se você deseja alocar algo que vive além do escopo da function, use um ponteiro inteligente (ou, em geral, um contêiner):

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

E agora o cliente armazena um ponteiro inteligente:

 std::unique_ptr x = getInt(); 

Referências também estão bem para acessar coisas onde você sabe que o tempo de vida está sendo mantido em um nível mais alto, por exemplo:

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

Aqui sabemos que não há problema em retornar uma referência a i_ porque o que quer que esteja nos chamando gerencia a vida útil da instância da class, então eu irei viver pelo menos por tanto tempo.

E, claro, não há nada errado com apenas:

 int getInt() { return 0; } 

Se o tempo de vida deve ser deixado para o chamador, e você está apenas calculando o valor.

Resumo: não há problema em retornar uma referência se o tempo de vida do object não terminar após a chamada.

Não. Não, não, mil vezes não.

O que é mal é fazer uma referência a um object alocado dinamicamente e perder o ponteiro original. Quando você new um new object, você assume a obrigação de ter uma delete garantida.

Mas dê uma olhada, por exemplo, no operator< < : que deve retornar uma referência, ou

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

não vai funcionar.

Você deve retornar uma referência a um object existente que não está desaparecendo imediatamente e não pretende transferir nenhuma propriedade.

Nunca retorne uma referência a uma variável local ou algo assim, porque não estará lá para ser referenciada.

Você pode retornar uma referência a algo independente da function, que você não espera que a function de chamada assuma a responsabilidade de excluir. Esse é o caso da function típica operator[] .

Se você está criando algo, você deve retornar um valor ou um ponteiro (regular ou inteligente). Você pode retornar um valor livremente, já que está indo para uma variável ou expressão na function de chamada. Nunca retorne um ponteiro para uma variável local, pois ela desaparecerá.

Não é mal. Como muitas coisas em C ++, é bom se usado corretamente, mas há muitas armadilhas que você deve estar ciente ao usá-lo (como retornar uma referência a uma variável local).

Há coisas boas que podem ser alcançadas com ele (como map [name] = “hello world”)

Eu acho as respostas não satisfatórias, então vou adicionar meus dois centavos.

Vamos analisar os seguintes casos:

Uso errôneo

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

Isso é obviamente erro

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

Uso com variables ​​estáticas

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

Isso está certo, porque as variables ​​estáticas existem ao longo da vida útil de um programa.

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

Isso também é bastante comum ao implementar o padrão 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

Os contêineres da biblioteca padrão dependem muito do uso de operadores que retornam referência, por exemplo

 T & operator*(); 

pode ser usado nos seguintes

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

Acesso rápido a dados internos

Há momentos em que & pode ser usado para access rápido a dados internos

 Class Container { private: std::vector m_data; public: std::vector& data() { return m_data; } } 

com o uso:

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

No entanto, isso pode levar a armadilhas como esta:

 Container* cont = new Container; std::vector& 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! 

“retornar uma referência é ruim porque, simplesmente (pelo que entendi), é mais fácil errar a exclusão”

Não é verdade. Retornar uma referência não implica semântica de propriedade. Isto é, só porque você faz isso:

 Value& v = thing->getTheValue(); 

… não significa que você agora possui a memory referida por v;

No entanto, este é um código horrível:

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

Se você está fazendo algo assim porque “você não precisa de um ponteiro naquela instância” então: 1) apenas desrefere o ponteiro se você precisar de uma referência, e 2) você eventualmente precisará do ponteiro, porque você precisa novo com uma exclusão, e você precisa de um ponteiro para chamar delete.

Existem dois casos:

  • referência const – boa idéia, às vezes, especialmente para objects pesados ​​ou classs proxy, otimização de compiladores

  • referência não-const – idéia ruim, às vezes, quebra os encapsulamentos

Ambos compartilham o mesmo problema – pode potencialmente apontar para o object destruído …

Eu recomendaria usar pointers inteligentes para muitas situações em que você precisa retornar uma referência / ponteiro.

Além disso, observe o seguinte:

Existe uma regra formal – o C ++ Standard (seção 13.3.3.1.4 se você estiver interessado) afirma que um temporário só pode ser ligado a uma referência const – se você tentar usar uma referência não-const o compilador deve sinalizar isso como um erro.

Não só não é mal, às vezes é essencial. Por exemplo, seria impossível implementar o operador [] de std :: vector sem usar um valor de retorno de referência.

A referência de retorno é normalmente usada na sobrecarga do operador em C ++ para Objeto grande, porque retornar um valor precisa de uma operação de cópia. (na sobrecarga do executor, geralmente não usamos o ponteiro como valor de retorno)

Mas a referência de retorno pode causar problema de alocação de memory. Como uma referência ao resultado será passada para fora da function como uma referência ao valor de retorno, o valor de retorno não pode ser uma variável automática.

Se você quiser usar o retorno de referência, você pode usar um buffer de object estático. por exemplo

 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; } 

Dessa forma, você poderia usar a referência de retorno com segurança.

Mas você sempre pode usar o ponteiro em vez da referência para retornar o valor em functiong.

Além da resposta aceita:

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

Eu diria que este exemplo não está bem e deve ser evitado, se possível. Por quê? É muito fácil acabar com uma referência pendente .

Para ilustrar o ponto com um exemplo:

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

entrando na zona de perigo:

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

Eu acho que usando a referência como o valor de retorno da function é muito mais direto do que usando o ponteiro como o valor de retorno da function. Em segundo lugar Seria sempre seguro usar a variável estática à qual o valor de retorno se refere.

O melhor é criar um object e passá-lo como parâmetro de referência / ponteiro para uma function que aloca essa variável.

Alocar o object na function e retorná-lo como uma referência ou ponteiro (o ponteiro é mais seguro, no entanto) é uma má ideia, pois libera a memory no final do bloco de funções.

Função como lvalue (também conhecido como retorno de referências não-const) deve ser removido do C ++. É terrivelmente não intuitivo. Scott Meyers queria um min () com esse comportamento.

 min(a,b) = 0; // What??? 

o que não é realmente uma melhoria

 setmin (a, b, 0); 

Este último faz mais sentido.

Eu percebo que a function como lvalue é importante para streams de estilo C ++, mas vale a pena ressaltar que os streams de estilo C ++ são terríveis. Eu não sou o único que pensa isso … pelo que me lembro, Alexandrescu tinha um grande artigo sobre como fazer melhor, e acredito que o boost também tentou criar um método de E / S seguro de tipo melhor.

  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 } }; 

A function getPtr pode acessar a memory dinâmica após a exclusão ou até mesmo um object nulo. O que pode causar exceções de access incorreto. Em vez disso, getter e setter devem ser implementados e o tamanho verificado antes de retornar.

Eu me deparei com um problema real, onde era realmente mal. Essencialmente, um desenvolvedor retornou uma referência a um object em um vetor. Isso foi ruim!!!

Os detalhes completos sobre os quais escrevi no Janurary: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html

Sobre o código horrível:

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

Então, de fato, o ponteiro da memory perdeu após o retorno. Mas se você usar shared_ptr assim:

 int& getTheValue() { std::shared_ptr p(new int); return *p->get(); } 

Memória não perdida após o retorno e será liberada após a atribuição.