simples - variavel de outra classe c++




Chamando funções virtuais dentro de construtores (9)

Suponha que eu tenha duas classes C ++:

class A
{
public:
  A() { fn(); }

  virtual void fn() { _n = 1; }
  int getn() { return _n; }

protected:
  int _n;
};

class B : public A
{
public:
  B() : A() {}

  virtual void fn() { _n = 2; }
};

Se eu escrever o seguinte código:

int main()
{
  B b;
  int n = b.getn();
}

Pode-se esperar que n seja definido como 2.

Acontece que n está definido como 1. Por quê?


A razão é que objetos C ++ são construídos como cebolas, de dentro para fora. Superclasses são construídas antes de classes derivadas. Então, antes que um B possa ser feito, um A deve ser feito. Quando o construtor de A é chamado, ainda não é um B, então a tabela de funções virtuais ainda tem a entrada para a cópia de fn () de A.


Chamar funções virtuais de um construtor ou destruidor é perigoso e deve ser evitado sempre que possível. Todas as implementações de C ++ devem chamar a versão da função definida no nível da hierarquia no construtor atual e não mais.

O C ++ FAQ Lite aborda isso na seção 23.7 em detalhes muito bons. Sugiro ler isso (e o resto do FAQ) para uma continuação.

Excerto:

[...] Em um construtor, o mecanismo de chamada virtual é desabilitado porque a substituição de classes derivadas ainda não aconteceu. Objetos são construídos a partir da base, “base antes derivada”.

[...]

Destruição é feita “classe derivada antes da classe base”, então funções virtuais se comportam como em construtores: Apenas as definições locais são usadas - e nenhuma chamada é feita para substituir funções para evitar tocar a parte da classe derivada (agora destruída) do objeto.

EDIT corrigido mais para todos (obrigado litb)


Como já foi dito, os objetos são criados com base na construção. Quando o objeto base está sendo construído, o objeto derivado ainda não existe, portanto, uma substituição de função virtual não pode funcionar.

No entanto, isso pode ser resolvido com getters polimórficos que usam polimorfismo estático em vez de funções virtuais se seus getters retornarem constantes ou, de outra forma, puderem ser expressos em uma função de membro estático. Este exemplo usa CRTP ( https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ).

template<typename DerivedClass>
class Base
{
public:
    inline Base() :
    foo(DerivedClass::getFoo())
    {}

    inline int fooSq() {
        return foo * foo;
    }

    const int foo;
};

class A : public Base<A>
{
public:
    inline static int getFoo() { return 1; }
};

class B : public Base<B>
{
public:
    inline static int getFoo() { return 2; }
};

class C : public Base<C>
{
public:
    inline static int getFoo() { return 3; }
};

int main()
{
    A a;
    B b;
    C c;

    std::cout << a.fooSq() << ", " << b.fooSq() << ", " << c.fooSq() << std::endl;

    return 0;
}

Com o uso de polimorfismo estático, a classe base sabe a qual classe 'getter chamar quando a informação é fornecida em tempo de compilação.


Durante a chamada do construtor do objeto, a tabela do ponteiro de função virtual não é completamente construída. Fazer isso geralmente não lhe dará o comportamento esperado. Chamar uma função virtual nessa situação pode funcionar, mas não é garantido e deve ser evitado para ser portátil e seguir o padrão C ++.


Eu não estou vendo a importância da palavra-chave virtual aqui. b é uma variável com tipagem estática e seu tipo é determinado pelo compilador no momento da compilação. As chamadas de função não referenciariam a vtable. Quando b é construído, o construtor da sua classe pai é chamado, e é por isso que o valor de _n é definido como 1.


O padrão C ++ (ISO / IEC 14882-2014) diz:

Funções de membro, incluindo funções virtuais (10.3), podem ser chamadas durante a construção ou destruição (12.6.2). Quando uma função virtual é chamada direta ou indiretamente de um construtor ou de um destruidor, incluindo durante a construção ou destruição dos membros de dados não estáticos da classe, e o objeto ao qual a chamada se aplica é o objeto (chame-o x) em construção ou destruição, a função chamada é o overrider final na classe do construtor ou do destruidor e não uma sobreposição em uma classe mais derivada. Se a chamada de função virtual usar um acesso de membro de classe explícito (5.2.5) e a expressão de objeto se referir ao objeto completo de x ou um dos subobjetos de classe base desse objeto, mas não x ou um de seus subobjetos de classe base, o comportamento é indefinido .

Portanto, não invoque funções virtual de construtores ou destruidores que tentem chamar o objeto em construção ou destruição, pois a ordem de construção começa de base para derivada e a ordem de destruidores começa de derivada para classe de base .

Portanto, tentar chamar uma função de classe derivada de uma classe base em construção é perigoso. Da mesma forma, um objeto é destruído em ordem inversa da construção, portanto, tentar chamar uma função em uma classe mais derivada de um destruidor pode acessar recursos que já foi liberado.


Os vtables são criados pelo compilador. Um objeto de classe tem um ponteiro para sua vtable. Quando começa a vida, esse ponteiro vtable aponta para a vtable da classe base. No final do código do construtor, o compilador gera código para apontar novamente o ponteiro vtable para a vtable real da classe. Isso garante que o código de construtor que chama funções virtuais chame as implementações de classe base dessas funções, não a substituição na classe.


Outras respostas já explicaram porque virtual chamadas de função virtual não funcionam como esperado quando chamadas de um construtor. Em vez disso, gostaria de propor outro possível trabalho para obter um comportamento semelhante ao polimórfico do construtor de um tipo de base.

Ao adicionar um construtor de modelo ao tipo base, de tal forma que o argumento de modelo é sempre deduzido para ser o tipo derivado, é possível estar ciente do tipo concreto do tipo derivado. De lá, você pode chamar funções de membro static para esse tipo derivado.

Essa solução não permite que funções de membro não static sejam chamadas. Enquanto a execução está no construtor do tipo base, o construtor do tipo derivado nem sequer teve tempo de passar pela lista de inicialização do membro. A parte do tipo derivado da instância que está sendo criada não começou a ser inicializada. E como static funções de membro não- static quase certamente interagem com os membros de dados, seria incomum querer chamar as funções de membro não- static do tipo derivado do construtor do tipo de base.

Aqui está uma implementação de amostra:

#include <iostream>
#include <string>

struct Base {
protected:
    template<class T>
    explicit Base(const T*) : class_name(T::Name())
    {
        std::cout << class_name << " created\n";
    }

public:
    Base() : class_name(Name())
    {
        std::cout << class_name << " created\n";
    }


    virtual ~Base() {
        std::cout << class_name << " destroyed\n";
    }

    static std::string Name() {
        return "Base";
    }

private:
    std::string class_name;
};


struct Derived : public Base
{   
    Derived() : Base(this) {} // `this` is used to allow Base::Base<T> to deduce T

    static std::string Name() {
        return "Derived";
    }
};

int main(int argc, const char *argv[]) {

    Derived{};  // Create and destroy a Derived
    Base{};     // Create and destroy a Base

    return 0;
}

Este exemplo deve imprimir

Derived created
Derived destroyed
Base created
Base destroyed

Quando um Derived é construído, o comportamento do construtor Base depende do tipo dinâmico real do objeto que está sendo construído.


Você conhece o erro de falha do Windows Explorer ?! "Chamada de função virtual pura ..."
Mesmo problema ...

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

Como não há implementação para a função pureVitualFunction () e a função é chamada no construtor, o programa irá travar.





virtual-method