Passagem por valor ou referência, para o construtor de um C ++ que precisa armazenar uma cópia?
-
13-09-2019 - |
Pergunta
Se um C ++ (implícita ou explícita) construtor valor aceitar seu parâmetro (s) por valor ou referência-a-const, quando ele precisa para armazenar uma cópia do argumento (s) em seu objeto de qualquer forma?
Aqui está o exemplo mais curta que eu posso pensar de:
struct foo {
bar _b;
foo(bar [const&] b) // pass by value or reference-to-const?
: _b(b) { }
};
A idéia aqui é que eu quero para minimizar as chamadas para o bar do construtor copiar quando um objeto foo é criado, em qualquer uma das várias maneiras em que um objeto foo pode ter criado.
Por favor note que eu sei um pouco sobre cópia elisão e (Nomeado) Valor de retorno Optimization, e eu li 'Quer velocidade? passagem por valor' , no entanto eu não acho que o artigo aborda diretamente este caso de uso.
Editar:. eu deveria ser mais específico
Suponha que eu não posso saber o sizeof(bar)
, ou se ou não bar
é um princípio essencial, built-in type (bar
pode ser um parâmetro de modelo, e foo
pode ser um modelo de classe em vez de uma classe). Além disso, não assuma construtor que de foo
pode ser embutido (ou bar
de, para que o assunto). Não supor que eu, pelo menos, pode estar usando um compilador que implementos OVCR.
O que eu gostaria é para que haja uma possibilidade (dadas otimizações do compilador) que uma chamada como este irá invocar há chamadas para construtor de cópia de bar
qualquer (mesmo quando execução _b(b)
na lista de inicialização do foo
):
foo f = function_that_creates_and_returns_a_bar_object_using_rvo();
Existe alguma possibilidade (dado o C ++ 98 standard) que isso pode ser feito, e se assim for, é mais ou menos provável de trabalho, se foo
aceita seu parâmetro de referência-a-const em vez de por valor?
Solução
Em C ++ 98 e C ++ 03, você deve passar const& bar
e copie. Em C ++ 0x, você deve passar bar
e depois fazer um movimento (desde bar
tem um construtor movimento).
#include <utility>
struct foo
{
bar _b;
foo(bar b) : _b(std::move(b)) {}
};
Se você construir foo com um parâmetro lvalue, o construtor de cópia será chamado para criar uma b
cópia, e essa cópia será movido para _b
. Se você construir foo com um parâmetro de rvalue, construtor movimento de bar
será chamado a se mudar para b
, e, em seguida, ele será movido novamente em _b
.
Outras dicas
Todas as coisas são iguais, eu passar por const & para classes suficientemente complexas, eo valor para o POD e objetos simples.
Deitado as vantagens / desvantagens de passar por referência const em vez de passagem tradicional pelo valor
Positivos:
- Evita uma cópia (grande vantagem para objetos com uma cópia caro)
- ACCESS read-only
Negativos:
- Alguém pode Const lançar o const fora da referência, se eles realmente queriam
Mais importante com os pontos positivos é que você explicitamente controle quando a cópia acontece (no seu caso ao passar inicializar _B em seu inicializador lista). Pensando sobre os negativos ... Concordo que é um risco. Eu acho que quase todos os programadores bons vai se sentir sujo sobre emplying o const_cast. Além disso você pode obedientemente procurar const_cast e jogue os tomates para a pessoa lançando o const fora do argumento. Mas hey, você nunca sabe e quem tem tempo para assistir a um código como um falcão:?)
Meu opinião subjetiva é que nas classes suficientemente complexos e em ambientes onde o desempenho importa o benefício de evitar o construtor de cópia supera o risco. No entanto, para as classes realmente mudos e POD, eu tendem a fazer cópias dos dados e passar por valor.
Olhe este questão .
Use const T & arg
se sizeof(T)>sizeof(void*)
e uso T arg
se sizeof(T) <= sizeof(void*)
. Todos os tipos fundamentais deve ser a exceção a esta regra
Estilisticamente, eu diria que passar por referência é o melhor caminho.
Se o desempenho realmente importa, então não acho. Medi-lo.
Eu não tenho verificado que o padrão diz, mas tentando este empiricamente posso dizer-lhe que o GCC não otimizar a cópia de distância, independentemente do construtor aceita o argumento por valor ou por referência const.
Se, no entanto, o construtor leva uma referência const, consegue evitar uma cópia desnecessário ao criar foo a partir de um já existente bar objeto.
Para resumir:
Bar b = makeBar(); // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar(); // Copy constructor of Bar called once
FooByValue f3(b); // Copy constructor of Bar called twice
FooByRef f4(b); // Copy constructor of Bar called only once
Não é que eu sou um especialista compilador, mas eu acho que faz sentido que geralmente não podem OVCR um valor de retorno em um lugar arbitrário (como o campo de membro do foo objeto). Em vez disso, ele requer o alvo para estar no topo da pilha.
Estou assumindo bar
tem um construtor de cópia normal do formulário bar(const bar &b)
Tome uma referência const aqui. construtor de bar
, então, tomar essa referência, e executar a cópia. Total de cópias:. 1
Se você deixou a referência off, o compilador iria fazer uma cópia de b, passar a cópia para o construtor de foo
, o que, em seguida, passá-lo para bar
e copiar isso.
Honestamente, para este caso, a melhor opção é fazer a sua inline
construtor, construtor fornecida bar
não joga. Em seguida, basta passar por referência.
Além disso, como você suspeita, o seu artigo não se aplica aqui.
Mantenha um shared_pointer para barrar em sua classe e passá-lo como tal. Dessa forma, você nunca chama o construtor de cópia:.)