Множественное наследование и повторяющиеся вызовы функций - PullRequest
1 голос
/ 10 февраля 2011

У меня есть ситуация, похожая на Множественное наследование + беспорядок виртуальных функций . Я тиражировал код в менее сложной среде, чтобы продемонстрировать, о чем я запутался.

Я хочу знать, как C выполняет (но не обрабатывает) и B1 :: method, и B2 :: method, который, в свою очередь, выполняет унаследованные методы.

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

В любом случае, это безопасное или неопределенное поведение, какие подводные камни и т. Д .; и что может быть лучший способ сделать это.

#include <iostream>
#include <vector>

class A {
    static std::vector<A*> listeners;
public:
    static void propagate();
protected:
    A() {
        listeners.push_back(this);
    }
    ~A() {
        for (std::vector<A*>::iterator it = listeners.begin(); it != listeners.end(); ++it) {
            if (*it == this) {
                listeners.erase(it);
                break;
            }
        }
    }
    virtual void method()=0;
};

std::vector<A*> A::listeners;

void A::propagate() {
    for (unsigned int i=0; i < listeners.size(); ++i) {
        listeners[i]->method();
    }
}

class B1 : public A {
protected:
    B1() {}
    ~B1() {}
    void method() {
        B1inhmethod();
    }
    virtual void B1inhmethod() {}
};
class B2 : public A {
protected:
    B2() {}
    ~B2() {}
    void method() {
        B2inhmethod();
    }
    virtual void B2inhmethod() {}
};

class C : public B1, public B2 {
public:
    C() {}
    ~C() {}
    void B1inhmethod() {
        std::cout << "\nB1method in C";
    }
    void B2inhmethod() {
        std::cout << "\nB2method in C";
    }
};

int main() {
    C myclass;
    A::propagate();
    return 0;
}

выход:

B1inhmethod in C
B2inhmethod in C

Ответы [ 2 ]

2 голосов
/ 10 февраля 2011

Я думаю, что причина этого в том, что C наследует две копии A, одну от B1 и одну от B2. Диаграмма наследования выглядит следующим образом:

A       A
|       |
B1     B2
  \   /
   \ /
    C

Когда вы создаете C, он инициализирует оба базовых класса B1 и B2, оба из которых рекурсивно инициализируют свой базовый класс A. Это приводит к тому, что в главный список A добавляются два разных указателя - указатель на базовый объект B1 в C и указатель на базовый объект B2 в A. Они оба являются частью одного и того же объекта, а именно экземпляра C, но потому что логически два базовых объекта участвуют, вы получите два указателя. Затем, когда вы выполните итерацию по списку объектов A, вы найдете компонент B B в C и компонент B2 в C, и, следовательно, оба сообщения будут распечатаны.

Если это не то, что вам нужно, рассмотрите возможность виртуального наследования , которое позволило бы объектам B1 и B2 совместно использовать базовый объект A. Таким образом, только одна копия объекта будет добавлена ​​в основной список. Конечно, вы должны быть осторожны, если вы делаете это, потому что тогда вам потребуется C для реализации method, чтобы избежать двусмысленности; если вы не определили это, есть две method реализации, где ни одна из них явно не подходит для вызова.

0 голосов
/ 10 февраля 2011

Я предполагаю, что

void B1inhmethod() {
    std::cout << "\nB1inhmethod in C";
}
void B2inhmethod() {
    std::cout << "\nB2inhmethod in C";
}

на самом деле переопределяет B1method() и B2method() и это просто опечатка. И я также предполагаю, что ваш вопрос заключается в том, почему оба они называются.

Поскольку класс C наследует от B1 и B2, оба их конструктора будут вызваны, и оба они будут добавлены к вектору слушателей.

Тогда, поскольку нет никакой двусмысленности, из которой следует вызывать метод класса method, B1 и B2 наследуются только от A и используют простой полиморфизм, будут вызываться и B1Method, и B2Method.

...