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).