É melhor em C ++ passar por valor ou passar por referência constante?
Gostaria de saber qual é a melhor prática. Sei que a passagem por referência constante deve proporcionar melhor desempenho no programa porque você não está fazendo uma cópia da variável.
c++
variables
pass-by-reference
constants
pass-by-value
Matt Pascoe
fonte
fonte
Respostas:
Ela costumava ser geralmente recomendado melhores práticas 1 para passar uso por ref const para todos os tipos , exceto para builtin tipos (
char
,int
,double
, etc.), para iterators e para objetos de função (lambdas, classes decorrentesstd::*_function
).Isso era especialmente verdade antes da existência da semântica de movimentação . O motivo é simples: se você passou por valor, era necessário fazer uma cópia do objeto e, exceto para objetos muito pequenos, isso sempre é mais caro do que passar uma referência.
Com o C ++ 11, ganhamos a semântica de movimentos . Em poucas palavras, a semântica de movimentação permite que, em alguns casos, um objeto possa ser passado "por valor" sem copiá-lo. Em particular, esse é o caso quando o objeto que você está passando é um rvalue .
Por si só, mover um objeto ainda é pelo menos tão caro quanto passar por referência. No entanto, em muitos casos, uma função copiará um objeto internamente de qualquer maneira - ou seja, ela assumirá a propriedade do argumento. 2
Nessas situações, temos o seguinte compromisso (simplificado):
"Passar por valor" ainda faz com que o objeto seja copiado, a menos que o objeto seja um rvalor. No caso de um rvalue, o objeto pode ser movido, de modo que o segundo caso não é mais "copiar e depois mover", mas "mover e depois (potencialmente) mover novamente".
Para objetos grandes que implementam construtores de movimento adequados (como vetores, strings ...), o segundo caso é muito mais eficiente que o primeiro. Portanto, é recomendável usar a passagem por valor se a função se apropriar do argumento e se o tipo de objeto oferecer suporte à movimentação eficiente .
Uma nota histórica:
De fato, qualquer compilador moderno deve ser capaz de descobrir quando passar valor é caro e converter implicitamente a chamada para usar uma const ref, se possível.
Em teoria. Na prática, os compiladores nem sempre podem mudar isso sem interromper a interface binária da função. Em alguns casos especiais (quando a função está embutida), a cópia será realmente elidida se o compilador puder descobrir que o objeto original não será alterado através das ações na função.
Mas, em geral, o compilador não pode determinar isso, e o advento da semântica de movimentação no C ++ tornou essa otimização muito menos relevante.
1 Por exemplo, em Scott Meyers, C ++ eficaz .
2 Isso é especialmente verdadeiro para construtores de objetos, que podem receber argumentos e armazená-los internamente para fazer parte do estado do objeto construído.
fonte
Edit: Novo artigo de Dave Abrahams no cpp-next:
Quer velocidade? Passe por valor.
A passagem por valor para estruturas em que a cópia é barata tem a vantagem adicional de o compilador assumir que os objetos não possuem alias (não são os mesmos objetos). Usando passagem por referência, o compilador não pode assumir isso sempre. Exemplo simples:
o compilador pode otimizá-lo em
pois sabe que eg não compartilha o mesmo local. se g fosse uma referência (foo &), o compilador não poderia ter assumido isso. como gi poderia então ser aliasado por f-> ie ter que ter um valor de 7. então o compilador precisaria buscar novamente o novo valor de gi da memória.
Para regras mais práticas, aqui está um bom conjunto de regras encontradas no artigo Move Constructors (leitura altamente recomendada).
"Primitivo" acima significa basicamente tipos de dados pequenos, com alguns bytes de comprimento e que não são polimórficos (iteradores, objetos de função etc.) ou caros de copiar. Nesse artigo, há uma outra regra. A idéia é que, às vezes, alguém quer fazer uma cópia (caso o argumento não possa ser modificado), e outras, não deseja (caso queira usar o próprio argumento na função, se o argumento for temporário de qualquer maneira , por exemplo). O artigo explica em detalhes como isso pode ser feito. No C ++ 1x, essa técnica pode ser usada nativamente com suporte a idiomas. Até então, eu iria com as regras acima.
Exemplos: para tornar uma string em maiúscula e retornar a versão em maiúscula, sempre se deve passar por valor: é preciso tirar uma cópia dela de qualquer maneira (não foi possível alterar diretamente a referência const) - para melhor torná-la o mais transparente possível. o chamador e faça essa cópia com antecedência, para que ele possa otimizar o máximo possível - conforme detalhado nesse documento:
No entanto, se você não precisar alterar o parâmetro de qualquer maneira, considere-o com referência a const:
No entanto, se o objetivo do parâmetro é escrever algo no argumento, passe-o por referência não const
fonte
__restrict__
(que também pode trabalhar em referências) a fazer cópias excessivas. Pena que o C ++ padrão não adotou arestrict
palavra-chave do C99 .Depende do tipo. Você está adicionando a pequena sobrecarga de ter que fazer uma referência e desreferência. Para tipos com um tamanho igual ou menor que ponteiros que estão usando o copiador padrão, provavelmente seria mais rápido passar por valor.
fonte
Como foi apontado, depende do tipo. Para tipos de dados internos, é melhor passar por valor. Mesmo algumas estruturas muito pequenas, como um par de entradas, podem ter um desempenho melhor passando por valor.
Aqui está um exemplo, suponha que você tenha um valor inteiro e deseje passá-lo para outra rotina. Se esse valor foi otimizado para ser armazenado em um registro, se você quiser passar como referência, primeiro ele deve ser armazenado na memória e, em seguida, um ponteiro para a memória colocada na pilha para realizar a chamada. Se estava sendo transmitido por valor, tudo o que é necessário é o registro empurrado para a pilha. (Os detalhes são um pouco mais complicados que os dados, considerando diferentes sistemas de chamada e CPUs).
Se você está fazendo programação de modelos, geralmente é forçado a sempre passar por const ref, pois você não conhece os tipos que estão sendo transferidos. Passar penalidades por passar algo ruim por valor é muito pior do que as penalidades de passar um tipo embutido por const ref.
fonte
Isto é o que eu normalmente trabalho ao projetar a interface de uma função que não é de modelo:
Passe por valor se a função não desejar modificar o parâmetro e o valor for barato para copiar (int, double, float, char, bool, etc ... Observe que std :: string, std :: vector e o resto dos contêineres na biblioteca padrão NÃO são)
Passe pelo ponteiro const se o valor for caro para copiar e a função não desejar modificar o valor apontado e NULL é um valor que a função manipula.
Passe pelo ponteiro não-const se o valor for caro para copiar e a função quiser modificar o valor apontado e NULL é um valor que a função manipula.
Passe pela referência const quando o valor for caro para copiar e a função não desejar modificar o valor referido e NULL não seria um valor válido se um ponteiro fosse usado.
Passe por referência não const quando o valor for caro para copiar e a função desejar modificar o valor referido e NULL não seria um valor válido se um ponteiro fosse usado.
fonte
std::optional
à imagem e você não precisará mais de ponteiros.Parece que você recebeu sua resposta. Passar por valor é caro, mas fornece uma cópia para você trabalhar, se necessário.
fonte
Como regra, passar pela referência const é melhor. Mas se você precisar modificar o argumento da função localmente, use melhor a passagem por valor. Para alguns tipos básicos, o desempenho geralmente é o mesmo, tanto para passar por valor quanto por referência. Na verdade, faça referência internamente representada pelo ponteiro, é por isso que você pode esperar, por exemplo, que, para o ponteiro, as duas passagens sejam iguais em termos de desempenho, ou mesmo a passagem por valor possa ser mais rápida por causa de desreferência desnecessária.
fonte
Como regra geral, valor para tipos que não são de classe e referência const para classes. Se uma classe é realmente pequena, é provavelmente melhor passar por valor, mas a diferença é mínima. O que você realmente deseja evitar é passar alguma classe gigantesca por valor e duplicar tudo - isso fará uma enorme diferença se você estiver passando, por exemplo, um std :: vector com alguns elementos.
fonte
std::vector
na verdade, aloca seus itens na pilha e o próprio objeto vetorial nunca cresce. Oh espere. Se a operação fizer com que uma cópia do vetor seja feita, ela irá de fato duplicar todos os elementos. Isso seria ruim.sizeof(std::vector<int>)
é constante, mas passá-lo por valor ainda copiará o conteúdo na ausência de qualquer inteligência do compilador.Passe por valor para tipos pequenos.
Passe por referências const para tipos grandes (a definição de big pode variar entre máquinas) MAS, no C ++ 11, passe por valor se você for consumir os dados, pois você pode explorar a semântica de movimento. Por exemplo:
Agora o código de chamada faria:
E apenas um objeto seria criado e movido diretamente para o membro
name_
da classePerson
. Se você passar pela referência const, será necessário fazer uma cópia para colocá-laname_
.fonte
Diferença simples: - Na função, temos parâmetro de entrada e saída; portanto, se o seu parâmetro de entrada e saída de passagem for o mesmo, use a chamada por referência; caso contrário, se os parâmetros de entrada e saída forem diferentes, é melhor usar a chamada por valor.
exemplo
void amount(int account , int deposit , int total )
parâmetro de entrada: conta, parâmetro de saída de depósito: total
entrada e saída é chamada de uso diferente por vaule
void amount(int total , int deposit )
entrada total depósito saída total
fonte