LINUX.ORG.RU

virtual public наследование, есть вопросы

 


0

2

Изучаю множественное наследование:

Есть ли (желательно русскоязычные) источники у кого под рукой, как работает механизм виртуального наследования (под капотом )

Внешне то понятно, в любом учебнике есть, но как внутри реализовано - найти не получилось с первой попытки.

т.е. как там в vtbl(?) заносятся отсылки на члены базового класса (именно судя по всему на переменные методы все же будут сдублированны, т.е. D будет содержать 3 метода f() и еще свой ) как там формируются методы.

например немного не понятно, почему если

A { virtual void f(); };
B: virtual public A { void f();};
C: virtual public A { void f();};

D: public B, public C {}; не описать метод B: void f(); - то компилятор скажет типа не знаю какую ф-ю выбрать.

т.е. даже если в D void f() не нужен - нужно сделать его пустое тело.

Но при этом, если мы бы имели не виртуальное наследование то без void f() в D скомилялось бы.

★★★★★

Я не спец по C++, но у меня всё компиляется.

Может потому что я не то ввожу, что у вас в исходниках. Можно хоть нормально отформатировать текст сообщения и исправить опечатки? «A { virtual void f(); };» - это не C++. Чтобы можно было копи-пейст сделать и не работать над ошибками автора. Ведь эту тему будут, может быть, читать другие посетители форума, почему бы не проявить к ним уважение?

seiken ★★★★★
()
Последнее исправление: seiken (всего исправлений: 2)

При множественном наследовании результирующий класс состоит какбы из двух склееных частей

struct DImpl
{
  struct BImpl
  {
     VTable *D_Vtable_for_B;
     // A + B Data ...
  }
  struct CImpl
  {
     VTable *D_Vtable_for_B;
     // A + C Data ...
  }
}
Тоесть внутри объекта есть две разные таблици виртуальных методов, и this меняет свое значение в зависимости от того в какую ветку переходит управление.

Здесь возникает проблема если B и С имеют общего предка, тогда в D мы получим две копии A (внутри СImpl и внутри BImpl). Для решении этой проблемы применяют виртуальное наследование. При виртуальном наследование класс который наследуется виртуалньно выделяется в отдельный блок зарание, в вашем случае класс B будет иметь внутреннюю структуру:

struct BImpl
{
  VTable* B_VTable;
  // B data
  struct AImpl
  {
     VTable *B_VTable_for_A;
     //  A data here
  }
}

А соотвецтвенно D будет выглядеть как:

struct DImpl
{
  struct BImpl
  {
     VTable *D_Vtable_for_B;
     // B Data ...
  }
  struct CImpl
  {
     VTable *D_Vtable_for_B;
     // C Data ...
  }
  struct AImpl
  {
     VTable *D_VTable_for_A;
     //  A data here
  }
}
В этом случае у компилятора возникает проблема при формировании «D_VTable_for_A» он попросту не знает адресс какой функции положить в виртуальную таблицу для вызова A::f() поскольку эта функция перекрыта и в B и в C

zaz ★★★★
()
Ответ на: комментарий от zaz

А соотвецтвенно D будет выглядеть как:

Ага, только подобъект A имеет смысл положить в начало, потому что инициализируется он первым. Точная спецификация — Itanium C++ ABI. Там можно проникнуться многими деталями реализации и понять те или иные правила стандарта.

const86 ★★★★★
()
Ответ на: комментарий от const86

только подобъект A имеет смысл положить в начало

Это я не к месту. В случае, когда виртуально наследуется только A, он и правда в конце окажется.

const86 ★★★★★
()

У тебя B::f() и C::f() являются конечными замещениями (final overriders) для A::f(). D наследует оба конечных замещения и не определяет своего. Это ошибка.

http://en.cppreference.com/w/cpp/language/virtual

Мне кажется это не должно зависеть от виртуального/невиртуального наследования.

iliyap ★★★★★
()

Каждый класс с виртуальными функциями (в том числе унаследованными) имеет скрытое поле vptr, указывающее на vtable этого класса. vtable содержит указатели на виртуальные функции. Все функции, на которые есть указатель в vtable класса, принимают указатель на объект именно этого класса. Если компилятору надо вставить в vtable указатель на унаследованную виртуальную функцию, а указатель на объект класса не тождественен указателю на объект базового класса, из которого унаследована функция, компилятор генерирует функцию-переходник, и вставляет в vtable указатель на нее.

iliyap ★★★★★
()
Ответ на: комментарий от zaz

почти понял (наверное), буду осмыслять после выходных)

bonta ★★★★★
() автор топика
Ответ на: комментарий от const86

спасибо) сохраню ссылку)

bonta ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

код от фонаря

код от фонаря - т.е. не что-то практически полезное, а просто попытки поиграться с тематикой (прочитав про виртуальное наследование)

#include <iostream>
#include <cstring>


using namespace std;


class Base
{
    int    i1;
protected:
    char * name;
public:
    int xx;
    Base ( int i = 10 );
    Base ( Base & In );
    virtual void Show ( );
    char * get_name ( ) { return name; }
    int & get_i () { return i1; }
    ~Base () { delete [ ] name; }
};


class Derived_A : virtual public Base
{
    int i2;
public:
    Derived_A ( int val = 0 ): i2 ( val )
    { name = new char [strlen("Derived Class")+1]; strcpy(name,"Derived Class"); }
    Derived_A ( Derived_A & In ): Base (In) { i2 = In.i2 - 1; }
    void Show ( );
};

class Derived_B : virtual public Base
{
    int i2;
public:
    Derived_B ( int val = 1 ): i2 ( val )
    { name = new char [strlen("Derived Class")+1]; strcpy(name,"Derived Class"); }
    Derived_B ( Derived_B & In ): Base (In) { i2 = In.i2 - 1; }
    void Show ( ); // если есть этот метод то при вирт наследовании при
                   // компиляции будет error: no unique final overrider
                   // for ‘virtual void Base::Show()’ in ‘Derived_C’
};


class Derived_C : public Derived_A, public Derived_B
{
public:
    //если не virtual в B и А
    /*Derived_C ( int val = 1 )
    {
        Derived_B::name = new char [strlen("Derived Class")+val];
        strcpy(Derived_B::name,"Derived Class");
    }*/
    Derived_C ( int val = 1 )
    { name = new char [strlen("Derived Class")+val]; strcpy(name,"Derived Class"); }
    string get_name() { cout << "Derived_C" << endl; return string("Derived_C");}
    void Show() {cout << "CC" << endl;};
};


void PrintMessage ( Base * data )
{
    data->Show();
}


int main ( void )
{
    Derived_A Class_Two_1;

    Derived_A Class_Two_2(-10);

    Derived_A Class_Two_3(Class_Two_2);

    Base      Class_One;

    cout << "========" << endl;
    PrintMessage (&Class_Two_2);
    cout << "========" << endl;
    PrintMessage (&Class_Two_3);
    cout << "========" << endl;
    PrintMessage (&Class_One);
    cout << "========" << endl;
    
    Derived_C M_I;
    
    cout << "Derived_C:" << endl;
    //можем использовать при virtual наследовании
    cout << M_I.get_i() << endl;
    M_I.Derived_B::Show();
    M_I.Show();
    cout << M_I.xx << endl;
    
    //а без virtual только так (для А или В) c virtual это тоже работает ( члены - методы все же создются )
    //(в отличии от членов переменных)
    //cout << M_I.Derived_A::get_i() << endl; 
    //M_I.Derived_B::Show();
    //cout << M_I.Derived_A::xx << endl;
    
//Вывод если метод содержится в базовом классе и в производных не переопределяется его
//можно использовать
//в наследованных от двух (и более) базовых которые наследованны от одного общего класса
//Если же переопределяется то нельзя - 
//no unique final overrider for ‘virtual void Base::Show()’ in ‘Derived_C’
 
//В случае использования virtual public наследования чтобы избежать ругания компилятора
// на невозможностьпереопеделить метод - нужно его объявить в производном классе
    
    
  
    return 0;
}

Base::Base (int i): i1 ( i )
{
    xx = -1;
    name = new char [ strlen("Base Class") + 1];
    strcpy ( name, "Base Class");
}

Base::Base ( Base & In ): i1 ( In.i1 )
{
    name = new char [ strlen(In.name) + 1];
    strcpy ( name, In.name);
}

void Base::Show ( )
{
    cout << "i_val = " << i1 << " name = \"" << name << "\"" << endl;
}

void Derived_A::Show ( )
{
    cout << "AA i_val = " << get_i( ) << " name = \"" << get_name( ) << "\"" << endl;
    cout << "i2_val = " << i2 << endl;
}

void Derived_B::Show ( )
{
    cout << "BB i_val = " << get_i( ) << " name = \"" << get_name( ) << "\"" << endl;
    cout << "i2_val = " << i2 << endl;
}

bonta ★★★★★
() автор топика
Ответ на: комментарий от iliyap

Мне кажется это не должно зависеть от виртуального/невиртуального наследования.

в моём экспериментике получилось что влияет (если заккоментить вируальное наследование и ф-ю в производном (от 2х классов) классе то будет работать а с виртуальным нет.

У тебя B::f() и C::f() являются конечными замещениями (final overriders) для A::f(). D наследует оба конечных замещения и не определяет своего.

Это почти понятно, не понятно то почему если два предка имеют финальное замещение его должен иметь и потомок.

Возможно потому что типа это объекты одной иерархии - и если в базовом классе есть отсылка на некоторый метод void f() и она переопределяется в классах потомках B и С --- то класс D как бы не знает (если сам не замещает этот метод ) что использовать (неоднозначность) - поэтому и ругается. Правильно понимаю?

При этом если (я это точно экспериментально вывел) например C не переопределял бы f() то тогда все ок

(неоднозначности нет - видимо в данном случае из A::f и B::f берется та которая ближе к порожденному, а если бы была еще и C::f - то типа непонятно какую выбрать и нужно своё замещение?)

Но при этом, если убрать виртуальное наследование то все окей.

(в книжках пишут что при невиртуальном наследовании типа дубляжи всех членов классов идут (методов и переменных) а при виртуальных дубляжей нет. Но как мне кажется это же только к переменным относится а не к методам. Ведь если у нас класс D потомок и C и B и косвенно А - то он должен обладать всеми методами иерархии.

Т.е. вне зависимости от виртуального или невиртуального множественного наследования в порожденный включаются все методоты всей иерархии - кроме полей- поля в еденичном экземпляре - если не прав - поправьте.

Ну и вот - хочется понять и логическую модель почему нужны «конечные замещения», как мне кажется логически можно и без них. Так же хочется и технические детали реализации знать ( например виртуальные методы - имеют в объектах не прямые адреса на память как ф-ии в си, а через указатель vptr на vtbl - и что-то подобное про механизм(реализацию) вирт наследования. Тут уже накидали материалы к изучению ребята, спасибо, буду читать на досуге, сейчас пока голова замылилась немного )

#include <iostream>
#include <cstring>


using namespace std;


class Base
{
    int    i1;
protected:
    char * name;
public:
    int xx;
    Base ( int i = 10 );
    Base ( Base & In );
    virtual void Show ( );
    char * get_name ( ) { return name; }
    int & get_i () { return i1; }
    ~Base () { delete [ ] name; }
};


class Derived_A : /*virtual*/ public Base
{
    int i2;
public:
    Derived_A ( int val = 0 ): i2 ( val )
    { name = new char [strlen("Derived Class")+1]; strcpy(name,"Derived Class"); }
    Derived_A ( Derived_A & In ): Base (In) { i2 = In.i2 - 1; }
    void Show ( );
};

class Derived_B : /*virtual*/ public Base
{
    int i2;
public:
    Derived_B ( int val = 1 ): i2 ( val )
    { name = new char [strlen("Derived Class")+1]; strcpy(name,"Derived Class"); }
    Derived_B ( Derived_B & In ): Base (In) { i2 = In.i2 - 1; }
    void Show ( ); // если есть этот метод то при вирт наследовании при
                   // компиляции будет error: no unique final overrider
                   // for ‘virtual void Base::Show()’ in ‘Derived_C’
};


class Derived_C : public Derived_A, public Derived_B
{
public:
    //если не virtual в B и А
    Derived_C ( int val = 1 )
    {
        Derived_B::name = new char [strlen("Derived Class")+val];
        strcpy(Derived_B::name,"Derived Class");
    }
    //Derived_C ( int val = 1 )
    //{ name = new char [strlen("Derived Class")+val]; strcpy(name,"Derived Class"); }
    string get_name() { cout << "Derived_C" << endl; return string("Derived_C");}
    //void Show() {cout << "CC" << endl;};
};


void PrintMessage ( Base * data )
{
    data->Show();
}


int main ( void )
{
    Derived_A Class_Two_1;

    Derived_A Class_Two_2(-10);

    Derived_A Class_Two_3(Class_Two_2);

    Base      Class_One;

    cout << "========" << endl;
    PrintMessage (&Class_Two_2);
    cout << "========" << endl;
    PrintMessage (&Class_Two_3);
    cout << "========" << endl;
    PrintMessage (&Class_One);
    cout << "========" << endl;
    
    Derived_C M_I;
    
    cout << "Derived_C:" << endl;
    //можем использовать при virtual наследовании
    //cout << M_I.get_i() << endl;
    //M_I.Derived_B::Show();
    //M_I.Show();
    //cout << M_I.xx << endl;
    
    //а без virtual только так (для А или В) c virtual это тоже работает ( члены - методы все же создются )
    //(в отличии от членов переменных)
    cout << M_I.Derived_A::get_i() << endl; 
    M_I.Derived_B::Show();
    cout << M_I.Derived_A::xx << endl;
    
     
    return 0;
}

Base::Base (int i): i1 ( i )
{
    xx = -1;
    name = new char [ strlen("Base Class") + 1];
    strcpy ( name, "Base Class");
}

Base::Base ( Base & In ): i1 ( In.i1 )
{
    name = new char [ strlen(In.name) + 1];
    strcpy ( name, In.name);
}

void Base::Show ( )
{
    cout << "i_val = " << i1 << " name = \"" << name << "\"" << endl;
}

void Derived_A::Show ( )
{
    cout << "AA i_val = " << get_i( ) << " name = \"" << get_name( ) << "\"" << endl;
    cout << "i2_val = " << i2 << endl;
}

void Derived_B::Show ( )
{
    cout << "BB i_val = " << get_i( ) << " name = \"" << get_name( ) << "\"" << endl;
    cout << "i2_val = " << i2 << endl;
}
bonta ★★★★★
() автор топика

Искусственнопроблемы, искусственнопроблемушки...

virtual public

Изучаю множественное наследование

Забей, на практике оно ненужно... Точнее, нужно только ССЗБ, которые пытаются избежать неизбежного ромбика, пользуясь почем зря множ. наследованием в развесистых иерархиях, хотя прекрасно можно обойтись без него (и/или без них). Ну еще помогает умничающим говноедам, слаще репы ничего не евшим, застрявшим в 90-х, на собеседованиях казаться умнее чем на самом деле :)

slackwarrior ★★★★★
()

Есть ли (желательно русскоязычные) источники у кого под рукой, как работает механизм виртуального наследования (под капотом ).

Брюс Эккель, «Философия С++»

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.