Digamos que eu tenho uma função que recebe um std::function
:
void callFunction(std::function<void()> x)
{
x();
}
Em vez disso, devo passar x
pela referência constante ?:
void callFunction(const std::function<void()>& x)
{
x();
}
A resposta a esta pergunta muda dependendo do que a função faz com ela? Por exemplo, se é uma função ou construtor de membro da classe que armazena ou inicializa o std::function
em uma variável de membro.
sizeof(std::function)
mais2 * sizeof(size_t)
, que é o menor tamanho que você consideraria para uma referência const.std::function
invólucro é tão importante quanto a complexidade de copiá-lo. Se cópias profundas estiverem envolvidas, pode ser muito mais caro do quesizeof
sugere.move
a função?operator()()
éconst
assim que uma referência const deve funcionar. Mas eu nunca usei std :: function.Respostas:
Se você deseja desempenho, passe por valor se estiver armazenando.
Suponha que você tenha uma função chamada "execute isso no thread da interface do usuário".
que executa algum código no thread "ui" e sinaliza
future
quando terminar. (Útil em estruturas de interface do usuário em que o segmento da interface do usuário é onde você deve mexer com os elementos da interface do usuário)Temos duas assinaturas que estamos considerando:
Agora, é provável que as usemos da seguinte maneira:
que criará um fechamento anônimo (um lambda), constrói um a
std::function
partir dele, passa-o para arun_in_ui_thread
função e aguarda a conclusão da execução no encadeamento principal.No caso (A), o
std::function
é construído diretamente a partir do nosso lambda, que é então usado dentro dorun_in_ui_thread
. O lambda émove
d dentro dostd::function
, portanto, qualquer estado móvel é eficientemente transportado para ele.No segundo caso, um temporário
std::function
é criado, o lambda émove
d, e esse temporáriostd::function
é usado por referência dentro dorun_in_ui_thread
.Até agora, tudo bem - os dois têm desempenho idêntico. Exceto
run_in_ui_thread
que ele fará uma cópia do seu argumento de função para enviar ao thread da interface do usuário para executar! (ele retornará antes de terminar, portanto, não pode apenas usar uma referência a ele). Para o caso (A), nós simplesmentemove
ostd::function
em seu armazenamento a longo prazo. No caso (B), somos forçados a copiar o arquivostd::function
.Essa loja torna a passagem por valor mais ideal. Se houver alguma possibilidade de você estar armazenando uma cópia do
std::function
, passe por valor. Caso contrário, qualquer uma das maneiras é aproximadamente equivalente: a única desvantagem do valor é se você estiver usando o mesmo volumestd::function
e tendo um sub método após o outro. Salvo isso, amove
será tão eficiente quanto aconst&
.Agora, existem outras diferenças entre as duas que mais se destacam se tivermos um estado persistente dentro da
std::function
.Suponha que o
std::function
objeto seja armazenado com aoperator() const
, mas também possui algunsmutable
membros de dados que ele modifica (que rude!).No
std::function<> const&
caso, osmutable
membros de dados modificados serão propagados para fora da chamada de função. Nostd::function<>
caso, eles não vão.Este é um caso de canto relativamente estranho.
Você quer tratar
std::function
como faria com qualquer outro tipo móvel, possivelmente pesado e barato. Mover é barato, copiar pode ser caro.fonte
const&
vejo apenas o custo da operação de cópia.std::function
é criado a partir do lambda. Em (A), o temporário é elidido no argumento pararun_in_ui_thread
. Em (B) uma referência ao referido temporário é passada pararun_in_ui_thread
.std::function
Desde que seus s sejam criados a partir de lambdas como temporários, essa cláusula é válida. O parágrafo anterior trata do caso em que o problemastd::function
persiste. Se nós não estão armazenando, apenas a criação de um lambda,function const&
efunction
tem exatamente o mesmo teto.run_in_ui_thread()
. Existe apenas uma assinatura para dizer "Passe por referência, mas não armazenarei o endereço"?std::future<void> run_in_ui_thread( std::function<void()>&& )
Se você está preocupado com o desempenho e não está definindo uma função de membro virtual, provavelmente não deve usá
std::function
-lo.Tornar o tipo de functor um parâmetro de modelo permite uma otimização maior do que
std::function
, inclusive a inclusão da lógica do functor. O efeito dessas otimizações provavelmente superará em muito as preocupações de copiar contra indireto sobre como passarstd::function
.Mais rápido:
fonte
std::forward<Functor>(x)();
, para preservar a categoria de valor do functor, já que é uma referência "universal". Não fará diferença em 99% dos casos.callFunction(std::move(myFunctor));
std::move
lo se não precisar mais dele de outra maneira ou passar diretamente se não desejar sair do objeto existente. As regras de recolhimento de referência garantem quecallFunction<T&>()
tenha um parâmetro do tipoT&
, nãoT&&
.Como de costume no C ++ 11, passar por value / reference / const-reference depende do que você faz com seu argumento.
std::function
não é diferente.A passagem por valor permite mover o argumento para uma variável (geralmente uma variável de membro de uma classe):
Quando você sabe que sua função moverá seu argumento, esta é a melhor solução, assim seus usuários poderão controlar como eles chamam sua função:
Acredito que você já conhece a semântica das referências (não) const, por isso não abordarei o assunto. Se você precisar de mais explicações, basta perguntar e eu atualizarei.
fonte