Множественное наследование виртуальных классов - PullRequest
0 голосов
/ 28 июля 2011

Предположим, у меня есть следующий код:

class a {
public:
    virtual void do_a() = 0;
}

class b {
public:
    virtual void do_b() = 0;
}

class c: public a, public b {
public:
    virtual void do_a() {};
    virtual void do_b() {};
}

a *foo = new c();
b *bar = new c();

Будут ли работать foo->do_a() и bar->do_b()?Какая здесь память?

Ответы [ 7 ]

4 голосов
/ 28 июля 2011

Будут ли работать a-> do_a () и b-> do_b ()?

Предполагая, что вы имели в виду foo->do_a() и bar->do_b(), поскольку a и b не объект , они тип , да. Они будут работать Вы пытались запустить это?

Какая здесь память?

В основном это определяется реализацией. К счастью, вам не нужно знать об этом, если вы не хотите писать непереносимый код.

3 голосов
/ 28 июля 2011

Почему бы и нет?Структура памяти обычно будет выглядеть примерно так:

+----------+
|  A part  |
+----------+
|  B part  |
+----------+
|  C part  |
+----------+

Если вы преобразуете свои foo и bar в void* и отображаете их, вы получите разные адреса, но компилятор это знает,и организует правильную фиксацию указателя this при вызове функции.

2 голосов
/ 28 июля 2011

Как уже упоминалось, следующее будет работать без проблем

foo->do_a();
bar->do_b();

Они, однако, не будут компилироваться

bar->do_a();
foo->do_b();

Поскольку bar относится к типу b*, он не знает do_a. То же самое верно для foo и do_b. Если вы хотите сделать эти вызовы функций, вы должны понизить.

static_cast<c *>(foo)->do_b();
static_cast<c *>(bar)->do_a();

Другая очень важная вещь, которая не показана в вашем примере кода, заключается в том, что при наследовании и обращении к производному классу через указатель базового класса базовый класс ДОЛЖЕН иметь виртуальный деструктор . Если этого не произойдет, следующее приведет к неопределенному поведению.

a* foo = new c();
delete a;

Исправление простое

class a {
public:
    virtual void do_a() = 0;

    virtual ~a() {}
};

Конечно, это изменение также необходимо внести в b.

1 голос
/ 28 июля 2011

Да, конечно, это будет работать. Механика немного сложнее, хотя. Объект будет иметь две таблицы: одну для родительского класса, а другую для родительского класса b. Указатели будут отрегулированы так, чтобы они указывали на подмножество объекта, которое соответствует типу указателя, что приводит к этому удивительному результату:

c * baz = new c;
a * foo = baz;
b * bar = baz;
assert((void *)foo == (void *)bar); // assertion fails!

Компилятор знает типы во время назначения и точно знает, как настроить указатели.

Это, конечно, полностью зависит от компилятора; ничто в стандарте C ++ не говорит, что это должно работать таким образом. Только то, что это должно работать.

1 голос
/ 28 июля 2011
foo->do_a();   // will work
bar->do_b();   // will work
bar->do_a();   // compile error (do_a() is not a member of B)
foo->do_b();   // compile error (do_b() is not a member of A)

// If you really know the types are correct:
C* c = static_cast<C*>(foo);

c->do_a();  // will work
c->do_b();  // will work

// If you don't know the types, you can try at runtime:
if(C* c = dynamic_cast<C*>(foo))
{
    c->do_a();  // will work
    c->do_b();  // will work
}
0 голосов
/ 28 июля 2011

Они будут работать. С точки зрения памяти, это зависит от реализации. Вы создали объекты в куче, и для большинства систем стоит отметить, что объекты в куче растут вверх (например, стек растет вниз). Так что возможно , у вас будет:

Memory:

+foo+
-----
+bar+
0 голосов
/ 28 июля 2011

Будут ли работать a-> do_a () и b-> do_b ()?

No.

Будут ли foo->do_a() и bar->do_b() работать?

Да.Ваш код является каноническим примером виртуальной диспетчеризации функций.

Почему вы просто не попробовали это?

Какой здесь порядок памяти?

Кому интересно?

(то есть это определяется реализацией и абстрагируется от вас. Вам не нужно ни знать, ни хотеть знать.)

...