Como “retornar um object” em C ++?

Eu sei que o título soa familiar, pois há muitas perguntas semelhantes, mas eu estou pedindo um aspecto diferente do problema (eu sei a diferença entre ter coisas na pilha e colocá-las no heap).

Em Java posso sempre retornar referências a objects “locais”

public Thing calculateThing() { Thing thing = new Thing(); // do calculations and modify thing return thing; } 

Em C ++, para fazer algo parecido tenho 2 opções

(1) posso usar referências sempre que preciso “retornar” um object

 void calculateThing(Thing& thing) { // do calculations and modify thing } 

Então use-o assim

 Thing thing; calculateThing(thing); 

(2) Ou eu posso retornar um ponteiro para um object alocado dinamicamente

 Thing* calculateThing() { Thing* thing(new Thing()); // do calculations and modify thing return thing; } 

Então use-o assim

 Thing* thing = calculateThing(); delete thing; 

Usando a primeira abordagem, não precisarei liberar memory manualmente, mas para mim é difícil ler o código. O problema com a segunda abordagem é, eu vou ter que lembrar de delete thing; , que não parece muito legal. Eu não quero retornar um valor copiado porque é ineficiente (eu acho), então aqui vem as perguntas

  • Existe uma terceira solução (que não requer copiar o valor)?
  • Existe algum problema se eu mantiver a primeira solução?
  • Quando e por que devo usar a segunda solução?

Eu não quero retornar um valor copiado porque é ineficiente

Prove isso.

Procurar RVO e NRVO, e em semântica de movimento C ++ 0x. Na maioria dos casos em C ++ 03, um parâmetro out é apenas uma boa maneira de tornar seu código feio e, em C ++ 0x, você realmente estaria se prejudicando usando um parâmetro out.

Basta escrever código limpo, retornar por valor. Se o desempenho for um problema, faça um perfil dele (pare de adivinhar) e encontre o que você pode fazer para solucioná-lo. É provável que não retorne as coisas das funções.


Dito isto, se você estiver decidido a escrever assim, provavelmente desejaria fazer o parâmetro out. Evita a alocação de memory dinâmica, que é mais segura e geralmente mais rápida. Isso requer que você tenha alguma maneira de construir o object antes de chamar a function, o que nem sempre faz sentido para todos os objects.

Se você quiser usar a alocação dinâmica, o mínimo que pode ser feito é colocá-lo em um ponteiro inteligente. (Isso deve ser feito o tempo todo de qualquer maneira) Então você não se preocupa com a exclusão de nada, as coisas são seguras contra exceções, etc. O único problema é que é provável que seja mais lento do que retornar por valor!

Basta criar o object e devolvê-lo

 Thing calculateThing() { Thing thing; // do calculations and modify thing return thing; } 

Eu acho que você fará um favor a si mesmo se esquecer a otimização e apenas escrever código legível (você precisará executar um profiler mais tarde – mas não pré-otimize).

Apenas retorne um object como este:

 Thing calculateThing() { Thing thing(); // do calculations and modify thing return thing; } 

Isso chamará o construtor de cópia no Things, então você pode querer fazer sua própria implementação disso. Como isso:

 Thing(const Thing& aThing) {} 

Isso pode ser um pouco mais lento, mas pode não ser um problema.

Atualizar

O compilador provavelmente otimizará a chamada para o construtor de cópia, portanto, não haverá sobrecarga extra. (Como dreamlax apontado no comentário).

Você tentou usar pointers inteligentes (se o Thing for um object muito grande e pesado), como auto_ptr:

 std::auto_ptr calculateThing() { std::auto_ptr thing(new Thing); // .. some calculations return thing; } // ... { std::auto_ptr thing = calculateThing(); // working with thing // auto_ptr frees thing } 

Uma maneira rápida de determinar se um construtor de cópia está sendo chamado é adicionar log ao construtor de cópia de sua class:

 MyClass::MyClass(const MyClass &other) { std::cout << "Copy constructor was called" << std::endl; } MyClass someFunction() { MyClass dummy; return dummy; } 

Ligue para o someFunction ; o número de "Construtor de cópia foi chamado" linhas que você obterá variará entre 0, 1 e 2. Se você não obtiver nenhum, então seu compilador otimizou o valor de retorno (o que é permitido fazer). Se você não obtiver 0, e seu construtor de cópia for ridiculamente caro, procure maneiras alternativas de retornar instâncias de suas funções.

Em primeiro lugar você tem um erro no código, você quer ter Thing *thing(new Thing()); e só return thing; .

  • Use shared_ptr . Deref como se fosse um ponteiro. Ele será excluído quando a última referência à Thing contida estiver fora do escopo.
  • A primeira solução é muito comum em bibliotecas ingênuas. Tem algum desempenho e sobrecarga sintática, evite se possível
  • Use a segunda solução somente se você puder garantir que nenhuma exceção será gerada ou quando o desempenho for absolutamente crítico (você estará interagindo com C ou assembly antes que isso se torne relevante).

Tenho certeza de que um especialista em C ++ terá uma resposta melhor, mas pessoalmente gosto da segunda abordagem. Usando pointers inteligentes ajuda com o problema de esquecer de delete e, como você diz, parece mais limpo do que ter que criar um object antes da mão (e ainda ter que excluí-lo se você quiser alocá-lo no heap).