Às vezes, noto programas que travam no meu computador com o erro: "chamada de função virtual pura".
Como esses programas compilam quando um objeto não pode ser criado de uma classe abstrata?
Às vezes, noto programas que travam no meu computador com o erro: "chamada de função virtual pura".
Como esses programas compilam quando um objeto não pode ser criado de uma classe abstrata?
Respostas:
Eles podem resultar se você tentar fazer uma chamada de função virtual de um construtor ou destruidor. Uma vez que você não pode fazer uma chamada de função virtual de um construtor ou destruidor (o objeto da classe derivada não foi construído ou já foi destruído), ele chama a versão da classe base, que no caso de uma função virtual pura, não não existe.
(Veja a demonstração ao vivo aqui )
class Base
{
public:
Base() { doIt(); } // DON'T DO THIS
virtual void doIt() = 0;
};
void Base::doIt()
{
std::cout<<"Is it fine to call pure virtual function from constructor?";
}
class Derived : public Base
{
void doIt() {}
};
int main(void)
{
Derived d; // This will cause "pure virtual function call" error
}
doIt()
chamada no construtor é facilmente desvirtualizada e enviada para Base::doIt()
estaticamente, o que apenas causa um erro de vinculador. O que realmente precisamos é uma situação em que o tipo dinâmico durante um despacho dinâmico seja o tipo de base abstrata.
Base::Base
chamar um não virtual f()
que, por sua vez, chama o doIt
método virtual (puro) .
Assim como o caso padrão de chamar uma função virtual do construtor ou destruidor de um objeto com funções virtuais puras, você também pode obter uma chamada de função virtual pura (pelo menos no MSVC) se chamar uma função virtual depois que o objeto foi destruído . Obviamente, isso é uma coisa muito ruim de se tentar, mas se você estiver trabalhando com classes abstratas como interfaces e bagunçar, então é algo que você pode ver. É possivelmente mais provável se você estiver usando interfaces de contagem referenciadas e tiver um bug de contagem de referência ou se tiver uma condição de corrida de uso / destruição de objeto em um programa multi-threaded ... O que há sobre esse tipo de chamada é que é muitas vezes é menos fácil entender o que está acontecendo, já que uma verificação dos 'suspeitos usuais' de chamadas virtuais em ctor e dtor aparecerá limpa.
Para ajudar a depurar esses tipos de problemas, você pode, em várias versões do MSVC, substituir o manipulador purecall da biblioteca de tempo de execução. Você faz isso fornecendo sua própria função com esta assinatura:
int __cdecl _purecall(void)
e vinculá-lo antes de vincular a biblioteca de tempo de execução. Isso dá a VOCÊ o controle do que acontece quando uma chamada direta é detectada. Depois de ter controle, você pode fazer algo mais útil do que o manipulador padrão. Eu tenho um manipulador que pode fornecer um rastreamento de pilha de onde a purecall aconteceu; veja aqui: http://www.lenholgate.com/blog/2006/01/purecall.html para mais detalhes.
(Observe que você também pode chamar _set_purecall_handler () para instalar seu manipulador em algumas versões do MSVC).
_purecall()
invocação que normalmente ocorre ao chamar um método de uma instância excluída não acontecerá se a classe base tiver sido declarada com a __declspec(novtable)
otimização (específico da Microsoft). Com isso, é inteiramente possível chamar um método virtual sobrescrito após o objeto ter sido excluído, o que poderia mascarar o problema até que ele o morda de alguma outra forma. A _purecall()
armadilha é sua amiga!
Normalmente, quando você chama uma função virtual por meio de um ponteiro pendente - provavelmente a instância já foi destruída.
Também pode haver razões mais "criativas": talvez você tenha conseguido cortar a parte do seu objeto onde a função virtual foi implementada. Mas geralmente é só que a instância já foi destruída.
Corri para o cenário que as funções virtuais puras são chamadas por causa de objetos destruídos, Len Holgate
já tenho uma resposta muito boa , gostaria de adicionar algumas cores com um exemplo:
O destruidor da classe Derived redefine os pontos vptr para a classe vtable da base, que tem a função virtual pura, portanto, quando chamamos a função virtual, ele realmente chama as virutais puras.
Isso pode acontecer devido a um bug de código óbvio ou a um cenário complicado de condição de corrida em ambientes de multi-threading.
Aqui está um exemplo simples (compilação g ++ com otimização desativada - um programa simples pode ser facilmente otimizado):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
E o rastreamento de pilha se parece com:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
Realçar:
se o objeto for totalmente excluído, o que significa que o destruidor é chamado e o memroy é recuperado, podemos simplesmente obter um, Segmentation fault
pois a memória voltou ao sistema operacional e o programa simplesmente não consegue acessá-lo. Portanto, esse cenário de "chamada de função virtual pura" geralmente ocorre quando o objeto é alocado no pool de memória, enquanto um objeto é excluído, a memória subjacente não é recuperada pelo SO, ela ainda está acessível ao processo.
Eu acho que há um vtbl criado para a classe abstrata por algum motivo interno (pode ser necessário para algum tipo de informação de tipo de tempo de execução) e algo dá errado e um objeto real consegue. É um bug. Só isso já deveria dizer que algo que não pode acontecer é.
Especulação pura
editar: parece que estou errado no caso em questão. OTOH IIRC algumas linguagens permitem chamadas vtbl do destruidor do construtor.
Eu uso o VS2010 e sempre que tento chamar o destrutor diretamente do método público, recebo um erro de "chamada de função virtual pura" durante o tempo de execução.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
Mudei o que está dentro de ~ Foo () para separar o método privado e funcionou perfeitamente.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
Se você usa Borland / CodeGear / Embarcadero / Idera C ++ Builder, pode apenas implementar
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
Durante a depuração, coloque um ponto de interrupção no código e veja a pilha de chamadas no IDE, caso contrário, registre a pilha de chamadas em seu manipulador de exceções (ou essa função) se você tiver as ferramentas apropriadas para isso. Eu pessoalmente uso MadExcept para isso.
PS. A chamada de função original está em [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
Aqui está uma maneira sorrateira de que isso aconteça. Isso essencialmente aconteceu comigo hoje.
class A
{
A *pThis;
public:
A()
: pThis(this)
{
}
void callFoo()
{
pThis->foo(); // call through the pThis ptr which was initialized in the constructor
}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual void foo()
{
}
};
B b();
b.callFoo();
I had this essentially happen to me today
obviamente não é verdade, porque simplesmente errado: uma função virtual pura é chamada apenas quando callFoo()
é chamada dentro de um construtor (ou destruidor), porque neste momento o objeto ainda está (ou já) em um estágio. Aqui está uma versão em execução do seu código sem o erro de sintaxe B b();
- os parênteses a tornam uma declaração de função, você quer um objeto.