Ponteiros para funções membro virtuais. Como funciona?
-
23-08-2019 - |
Pergunta
Considere o seguinte código C ++:
class A
{
public:
virtual void f()=0;
};
int main()
{
void (A::*f)()=&A::f;
}
Se eu tivesse que adivinhar, diria que o & A :: f neste contexto significaria "o endereço da implementação do A de f ()", já que não há separação explícita entre ponteiros para funções e membros regulares funções membro virtuais. E já que um não implementa f (), que seria um erro de compilação. No entanto, não é.
E não só isso. O código a seguir:
void (A::*f)()=&A::f;
A *a=new B; // B is a subclass of A, which implements f()
(a->*f)();
vai realmente chamar B :: f.
Como isso acontece?
Solução
Aqui é demais informações sobre ponteiros de função de membro. Há algumas coisas sobre funções virtuais em "Os bem-comportados Compiladores", embora IIRC quando li o artigo que eu estava roçando a parte, uma vez que o artigo é realmente sobre a implementação de delegados em C ++.
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
A resposta curta é que ele depende do compilador, mas uma possibilidade é que o ponteiro de função de membro é implementada como uma estrutura que contém um ponteiro para uma função de "conversão", que faz a chamada virtual.
Outras dicas
Ele funciona porque o padrão diz que é assim que deve acontecer. Eu fiz alguns testes com GCC, e verifica-se para as funções virtuais, CCG armazena a tabela virtual deslocamento da função em questão, em bytes.
struct A { virtual void f() { } virtual void g() { } };
int main() {
union insp {
void (A::*pf)();
ptrdiff_t pd[2];
};
insp p[] = { { &A::f }, { &A::g } };
std::cout << p[0].pd[0] << " "
<< p[1].pd[0] << std::endl;
}
Esse programa saídas 1 5
- os deslocamentos byte da tabela entradas virtuais dessas duas funções. Segue-se o Itanium C ++ ABI , que especifica que .
Eu não sou inteiramente certo, mas eu acho que é o comportamento polimórfico apenas regular. Eu acho que &A::f
realmente significa o endereço do ponteiro de função em vtable da classe, e é por isso que você não está recebendo um erro do compilador. O espaço no vtable ainda é alocada, e que é o local que você está realmente ficando para trás.
Isso faz sentido porque as classes derivadas essencialmente substituir estes valores com ponteiros para as suas funções. É por isso que (a->*f)()
trabalha em seu segundo exemplo -. f
está referenciando o vtable que é implementado na classe derivada