Eu recebi essa pergunta quando recebi um comentário de revisão de código dizendo que as funções virtuais não precisam estar embutidas.
Eu pensei que funções virtuais embutidas poderiam ser úteis em cenários em que funções são chamadas diretamente a objetos. Mas o contra-argumento me veio à mente: por que alguém iria querer definir virtual e depois usar objetos para chamar métodos?
É melhor não usar funções virtuais em linha, pois elas quase nunca são expandidas?
Fragmento de código que usei para análise:
class Temp
{
public:
virtual ~Temp()
{
}
virtual void myVirtualFunction() const
{
cout<<"Temp::myVirtualFunction"<<endl;
}
};
class TempDerived : public Temp
{
public:
void myVirtualFunction() const
{
cout<<"TempDerived::myVirtualFunction"<<endl;
}
};
int main(void)
{
TempDerived aDerivedObj;
//Compiler thinks it's safe to expand the virtual functions
aDerivedObj.myVirtualFunction();
//type of object Temp points to is always known;
//does compiler still expand virtual functions?
//I doubt compiler would be this much intelligent!
Temp* pTemp = &aDerivedObj;
pTemp->myVirtualFunction();
return 0;
}
pTemp->myVirtualFunction()
poderia ser resolvido como uma chamada não virtual, pode haver uma chamada em linha. Essa chamada referenciada é incorporada pelo g ++ 3.4.2:TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction();
Seu código não é.Respostas:
As funções virtuais podem ser incorporadas algumas vezes. Um trecho das excelentes perguntas frequentes sobre C ++ :
fonte
C ++ 11 foi adicionado
final
. Isso muda a resposta aceita: não é mais necessário saber a classe exata do objeto, é suficiente saber que o objeto tem pelo menos o tipo de classe em que a função foi declarada final:fonte
icc
parece fazê-lo, de acordo com esse link.Há uma categoria de funções virtuais em que ainda faz sentido tê-las em linha. Considere o seguinte caso:
A chamada para excluir 'base' realizará uma chamada virtual para chamar o destruidor correto da classe derivada; essa chamada não está embutida. No entanto, como cada destruidor chama seu destruidor pai (que nesses casos está vazio), o compilador pode incorporar essas chamadas, pois elas não chamam virtualmente as funções da classe base.
O mesmo princípio existe para construtores de classe base ou para qualquer conjunto de funções em que a implementação derivada também chame a implementação de classe base.
fonte
Eu já vi compiladores que não emitem nenhuma tabela v se nenhuma função não-inline existir (e definida em um arquivo de implementação em vez de um cabeçalho). Eles lançariam erros como
missing vtable-for-class-A
ou algo semelhante, e você ficaria confuso como o inferno, como eu era.De fato, isso não está em conformidade com o Padrão, mas isso acontece; considere colocar pelo menos uma função virtual que não esteja no cabeçalho (se apenas o destruidor virtual), para que o compilador possa emitir uma tabela de tabelas para a classe naquele local. Eu sei que isso acontece com algumas versões do
gcc
.Como alguém mencionado, as funções virtuais embutidas às vezes podem ser um benefício , mas é claro que você as usará com mais frequência quando não souber o tipo dinâmico do objeto, porque esse foi o motivo todo
virtual
em primeiro lugar.O compilador, no entanto, não pode ignorar completamente
inline
. Possui outras semânticas além de acelerar uma chamada de função. A linha implícita para definições em classe é o mecanismo que permite colocar a definição no cabeçalho: Somenteinline
funções podem ser definidas várias vezes em todo o programa, sem violar nenhuma regra. No final, ele se comporta como você o definiria apenas uma vez em todo o programa, mesmo que você tenha incluído o cabeçalho várias vezes em diferentes arquivos vinculados.fonte
Bem, na verdade, as funções virtuais sempre podem ser incorporadas , desde que estejam estaticamente vinculadas: suponha que tenhamos uma classe abstrata
Base
com uma função virtualF
e classes derivadasDerived1
eDerived2
:Uma chamada hipotética
b->F();
(comb
do tipoBase*
) é obviamente virtual. Mas você (ou o compilador ...) poderia reescrevê-lo dessa maneira (suponha quetypeof
seja umatypeid
função semelhante a que retorne um valor que possa ser usado em aswitch
)Embora ainda necessitemos de RTTI para a
typeof
chamada, a chamada pode ser efetivamente incorporada, basicamente, incorporando a vtable no fluxo de instruções e especializando a chamada para todas as classes envolvidas. Isso também pode ser generalizado, especializando apenas algumas classes (digamos, apenasDerived1
):fonte
Marcar um método virtual em linha ajuda a otimizar ainda mais as funções virtuais nos dois casos a seguir:
Padrão de modelo curiosamente recorrente ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt )
Substituindo métodos virtuais por modelos ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
fonte
o inline realmente não faz nada - é uma dica. O compilador pode ignorá-lo ou pode incorporar um evento de chamada sem linha, se vir a implementação e gostar dessa idéia. Se a clareza do código estiver em risco, a linha deve ser removida.
fonte
Funções virtuais declaradas embutidas são embutidas quando chamadas por meio de objetos e ignoradas quando chamadas por meio de ponteiro ou referências.
fonte
Com os compiladores modernos, não será prejudicial inibe-los. Alguns combos antigos de compilador / vinculador podem ter criado várias vtables, mas não acredito que isso seja um problema.
fonte
Um compilador só pode incorporar uma função quando a chamada pode ser resolvida sem ambiguidade no momento da compilação.
As funções virtuais, no entanto, são resolvidas no tempo de execução e, portanto, o compilador não pode incorporar a chamada, pois no tipo de compilação o tipo dinâmico (e, portanto, a implementação da função a ser chamada) não pode ser determinado.
fonte
Nos casos em que a chamada de função é inequívoca e a função é um candidato adequado para embutir, o compilador é inteligente o suficiente para embutir o código de qualquer maneira.
O resto do tempo "inline virtual" é um absurdo e, de fato, alguns compiladores não compilarão esse código.
fonte
Faz sentido criar funções virtuais e depois chamá-las em objetos, em vez de referências ou ponteiros. Scott Meyer recomenda, em seu livro "c ++ eficaz", nunca redefinir uma função não virtual herdada. Isso faz sentido, porque quando você cria uma classe com uma função não virtual e redefine a função em uma classe derivada, pode usá-la corretamente, mas não pode ter certeza de que outras pessoas a usarão corretamente. Além disso, você pode usá-lo incorretamente posteriormente. Portanto, se você cria uma função em uma classe base e deseja que seja redifinável, torne-a virtual. Se faz sentido criar funções virtuais e chamá-las em objetos, também faz sentido incorporá-las.
fonte
Na verdade, em alguns casos, adicionar "inline" a uma substituição final virtual pode fazer com que seu código não seja compilado; portanto, às vezes há uma diferença (pelo menos no compilador do VS2017s)!
Na verdade, eu estava executando uma função de substituição final em linha virtual no VS2017, adicionando o padrão c ++ 17 para compilar e vincular e, por algum motivo, falhou quando estou usando dois projetos.
Eu tinha um projeto de teste e uma DLL de implementação que eu sou teste de unidade. No projeto de teste, eu estou tendo um arquivo "linker_includes.cpp" que inclui os arquivos * .cpp do outro projeto necessário. Eu sei ... Eu sei que posso configurar o msbuild para usar os arquivos de objeto da DLL, mas lembre-se de que é uma solução específica da Microsoft, embora a inclusão dos arquivos cpp não esteja relacionada ao sistema de compilação e muito mais fácil para a versão um arquivo cpp que arquivos xml e configurações do projeto e ...
O interessante foi que eu estava constantemente recebendo erros do vinculador do projeto de teste. Mesmo se eu adicionasse a definição das funções ausentes, copie e cole e não por meio de include! Tão estranho. O outro projeto foi criado e não há conexão entre os dois além de marcar uma referência de projeto, portanto, há uma ordem de criação para garantir que ambos sejam sempre criados ...
Eu acho que é algum tipo de bug no compilador. Não faço ideia se ele existe no compilador enviado com o VS2020, porque estou usando uma versão mais antiga, porque alguns SDK funcionam apenas com isso adequadamente :-(
Eu só queria acrescentar que não apenas marcá-los como inline pode significar algo, mas pode até fazer com que seu código não seja compilado em algumas circunstâncias raras! Isso é estranho, mas é bom saber.
PS: O código no qual estou trabalhando é relacionado à computação gráfica, então prefiro inlining e é por isso que usei final e inline. Eu mantive o especificador final para esperar que a compilação do lançamento seja inteligente o suficiente para compilar a DLL, incluindo-a mesmo sem que eu indique diretamente ...
PS (Linux) .: Espero que o mesmo não aconteça no gcc ou no clang, pois eu costumava fazer esse tipo de coisa. Não sei de onde vem esse problema ... Prefiro fazer c ++ no Linux ou pelo menos com alguns gcc, mas às vezes o projeto é diferente em necessidades.
fonte