Pergunta

As referências em C ++ estão me desconcertantes. :)

A idéia básica é que estou tentando devolver um objeto de uma função. Eu gostaria de fazer isso sem devolver um ponteiro (porque então eu teria que delete ), e sem ligar para o constitui adicionado: E também porque me pergunto se não posso evitar escrever um construtor de cópias).

Então, apesar de tudo, aqui estão as opções para fazer isso que eu encontrei:

  • O tipo de retorno da função pode ser a própria classe (MyClass fun() { ... }) ou uma referência à classe (MyClass& fun() { ... }).
  • A função pode construir a variável na linha de retorno (return MyClass(a,b,c);) ou devolver uma variável existente (MyClass x(a,b,c); return x;).
  • O código que recebe a variável também pode ter uma variável de qualquer um dos tipos: (MyClass x = fun(); ou MyClass& x = fun();)
  • O código que recebe a variável pode criar uma nova variável na mosca (MyClass x = fun();) ou atribui -lo a uma variável existente (MyClass x; x = fun();)

E alguns pensamentos sobre isso:

  • Parece ser uma má ideia ter o tipo de retorno MyClass& Porque isso sempre resulta na variável que está sendo destruída antes de ser devolvida.
  • O construtor de cópias parece se envolver apenas quando eu retorno uma variável existente. Ao retornar uma variável construída na linha de retorno, ela nunca é chamada.
  • Quando atribuo o resultado a uma variável existente, o destruidor também sempre entra em ação antes que o valor seja retornado. Além disso, nenhum construtor de cópia é chamado, mas a Variável Target recebe os valores do membro do objeto retornado da função.

Esses resultados são tão inconsistentes que me sinto totalmente confuso. Então, o que exatamente está acontecendo aqui? Como devo construir e retornar corretamente um objeto a partir de uma função?

Foi útil?

Solução

A melhor maneira de entender a cópia no C ++ geralmente não é tentar produzir um exemplo artificial e instrumentá -lo - o compilador é permitido remover e adicionar chamadas de construtor de cópias, mais ou menos como achar adequado.

Resumindo - se você precisar retornar um valor, retorne um valor e não se preocupe com nenhuma "despesa".

Outras dicas

Leitura recomendada: C ++ eficaz por Scott Meyers. Você encontra uma explicação muito boa sobre esse tópico (e muito mais) lá.

Em resumo, se você retornar por valor, o construtor de cópias e o destruidor estarão envolvidos por padrão (a menos que o compilador os otimize - é o que acontece em alguns dos seus casos).

Se você retornar por referência (ou ponteiro) uma variável que é local (construída na pilha), você convida problemas porque o objeto é destruído após o retorno, para que você tenha uma referência pendurada como resultado.

A maneira canônica de construir um objeto em uma função e retornar é por valor, como:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Se você usar isso, não precisará se preocupar com problemas de propriedade, referências pendentes etc. e o compilador provavelmente otimizará as chamadas de construtor / destruidor de cópias extras para você, para que você também não precise se preocupar com o desempenho.

É possível retornar por referência um objeto construído por new (ou seja, na pilha) - Este objeto não será destruído ao retornar da função. No entanto, você precisa destruí -lo explicitamente em algum lugar mais tarde ligando delete.

Também é tecnicamente possível armazenar um objeto retornado por valor em uma referência, como:

MyClass& x = fun();

No entanto, Afaik não há muito sentido em fazer isso. Especialmente porque é possível passar facilmente essa referência a outras partes do programa que estão fora do escopo atual; No entanto, o objeto referenciado por x é um objeto local que será destruído assim que você deixar o escopo atual. Portanto, esse estilo pode levar a bugs desagradáveis.

Ler sobre Rvo e nrvo (em uma palavra que esses dois representam otimização de valor de retorno e nomeado RVO, e são técnicas de otimização usadas pelo compilador para fazer o que você está tentando alcançar)

Você encontrará muitos assuntos aqui no Stackoverflow

Se você criar um objeto como este:

MyClass foo(a, b, c);

Em seguida, estará na pilha no quadro da função. Quando essa função termina, seu quadro é retirado da pilha e todos os objetos nesse quadro são destruídos. Não há como evitar isso.

Portanto, se você deseja devolver um objeto a um chamador, apenas as opções são:

  • Retornar por valor - é necessário um construtor de cópia (mas a chamada para o construtor de cópias pode ser otimizada).
  • Retorne um ponteiro e certifique -se de usar ponteiros inteligentes para lidar com ele ou excluí -lo cuidadosamente quando for feito com ele.

Tentar construir um objeto local e, em seguida, retornar uma referência a essa memória local a um contexto de chamada não é coerente - um escopo de chamada não pode acessar a memória local ao escopo chamado. Essa memória local é válida apenas para a duração da função que a possui - ou, de outra maneira, enquanto a execução permanece nesse escopo. Você deve entender isso para programar em C ++.

Na única vez em que faz sentido retornar uma referência é se você está retornando uma referência a um objeto pré-existente. Para um exemplo óbvio, quase toda função de membro do iostream retorna uma referência ao iostream. O próprio iostream existe antes que qualquer uma das funções de membro seja chamada e continua a existir depois de serem chamadas.

O padrão permite "copiar elision", o que significa que o construtor de cópias não precisa ser chamado quando você retorna um objeto. Isso vem em duas formas: otimização de valor de retorno de nome (NRVO) e otimização de valor de retorno anônimo (geralmente apenas RVO).

Pelo que você está dizendo, seu compilador implementa o RVO, mas não o nrvo - o que significa que é provavelmente um compilador um pouco mais antigo. A maioria dos compiladores atuais implementa ambos. O DTOR não correspondente, nesse caso, significa que provavelmente é algo como o GCC 3.4 ou o que há-embora eu não me lembre da versão, com certeza, havia um por aí que tinha um bug como esse. Obviamente, também é possível que sua instrumentação não esteja certa, portanto, um CTOR que você não está sendo usado e um DTOR correspondente está sendo chamado para esse objeto.

No final, você está preso a um fato simples: se precisar devolver um objeto, precisará devolver um objeto. Em particular, uma referência só pode dar acesso a uma (possivelmente versão modificada de) um objeto existente - mas esse objeto também precisava ser construído em algum momento. Se você pode modificar algum objeto existente sem causar um problema, tudo bem e bem, vá em frente e faça -o. Se você precisar de um novo objeto diferente e separado daqueles que você já possui, vá em frente e faça isso-pré-criando o objeto e passar em uma referência a ele pode tornar o retorno mais rápido, mas não economizará tempo geral. Criar o objeto tem o mesmo custo, seja feito dentro ou fora da função. Qualquer compilador razoavelmente moderno incluirá o RVO, para que você não pague nenhum custo extra para criá -lo na função e depois devolvê -lo - o compilador apenas automatizará o espaço de alocação para o objeto onde ele será devolvido e terá a função Construa -o "no lugar", onde ainda estará acessível após o retorno da função.

Basicamente, o retorno de uma referência só faz sentido se o objeto ainda existir depois de deixar o método. O compilador o avisará se você retornar uma referência a algo que está sendo destruído.

Retornar uma referência em vez de um objeto por valor salva a cópia do objeto que pode ser significativo.

As referências são mais seguras que as dicas porque têm diferentes symantics, mas nos bastidores são indicadores.

Uma solução potencial, dependendo do seu caso de uso, é para construir o objeto fora da função, leve dentro uma referência a ele e inicialize o objeto referenciado dentro da função, assim:

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Agora, é claro, isso não funciona se não for possível (ou não faz sentido) Foo Objeta e, em seguida, inicialize mais tarde. Se for esse o caso, sua única opção real para evitar a construção de cópias é retornar um ponteiro a um objeto alocado por heap.

Mas então pense no motivo pelo qual você está tentando evitar a construção de cópias em primeiro lugar. A "despesa" da construção de cópias está realmente afetando seu programa ou esse é um caso de otimização prematura?

Você está preso com:

1) retornar um ponteiro

MyClass* func () {// Alguns stuf retornam new MyClass (a, b, c); }

2) retornar uma cópia do objeto myclass func () {retorna myclass (a, b, c); }

O retorno de uma referência não é válido porque o objeto deve ser destruído após a saída do escopo do FUNC, exceto se a função for um membro da classe e a referência for de uma variável que seja membro da classe.

Não é uma resposta direta, mas uma sugestão viável: você também pode retornar um ponteiro, embrulhado em um auto_ptr ou smart_ptr. Então você estará no controle do que os construtores e destruidores são chamados e quando.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top