C ++ множественное наследование: использование реализации базового класса A для абстрактного метода базового класса B - PullRequest
0 голосов
/ 28 июня 2018

У меня есть абстрактный класс Interface, который объявляет f() и g() методы.

MyClass реализует Interface::g(), а MyClass наследует от Base, который реализует свой собственный f().

Как я могу сделать Base::f() реализацией для Interface::f() в MyClass?

Я не хочу, чтобы Base наследовал от Interface напрямую, если это возможно.

class Interface {
    public:
        virtual void f() = 0;
        virtual void g() = 0;
};

class Base {
    public:
        void f() { }
};

class MyClass : public Interface, public Base {
    public:
        void g() { }
};

int main() {
    MyClass c;
    c.f();
    c.g();
}

Это не работает, потому что f все еще является чисто виртуальной функцией в MyClass.

В Java я бы использовал следующий подход, но он явно не работает в C ++.

public class MyClass extends Base implements Interface {
    public static void main(String argv[]) {
        MyClass c = new MyClass();
        c.f();
        c.g();
    }
    public void g() { }
}

class Base {
    public void f() { }
}

interface Interface {
    public void f();
    public void g();
}

Конкретная проблема, которую я пытаюсь решить, заключается в следующем:

Interface в приведенном выше примере является абстрактным классом Sender с различными методами, связанными с отправкой MIDI-сообщений, например, update() (проверьте, нужно ли отправлять сообщение, и отправьте его при необходимости) и setAddress(), чтобы изменить адрес сообщения. Сообщения являются MIDI-событиями. Я должен изменить адрес / канал для таких функций, как транспонирование и выбор разных банков.

Класс Base - это класс Button. У него есть метод update(), который проверяет, нажата ли кнопка или нет, и вызывает (виртуальные) методы press() и release() соответственно.

MyClass будет реализовывать методы press() и release(), чтобы отправлять правильные данные при изменении состояния кнопки.

Существуют и другие типы Sender с, и я сохраняю их все в списке, чтобы update() их сразу, независимо от конкретного типа Sender. Мне также нужно иметь возможность изменить адрес всех Sender в списке (все сразу).

Метод setAddress() не имеет ничего общего с кнопкой, поэтому нет смысла иметь Button наследованным от Sender.
Существуют также другие классы, которые реализуют Button, но они не все Sender s.

Может быть, мне придется полностью изменить структуру, но в настоящий момент я не вижу лучшего решения.

class Button {
    public:
        void update() {
            if ( /* check in hardware whether button is pressed */ )
                press();
        }
        virtual void press() = 0;
};

class Sender {
    public:
        virtual void update() = 0;
        virtual void setAddress(int address) = 0;
};

class ButtonSender : public Button, public Sender {
    public:
        void update() {
            Button::update();
        }
        void press() {
            /* send a message */
        }
        void setAddress(int address) {
            /* change the address */
        }
};

int main() {
    ButtonSender bs;
    Sender *s = &bs;
    s->update();
}

Ответы [ 4 ]

0 голосов
/ 29 июня 2018

Спасибо, ОП, за публикацию более конкретного примера. Это заставило меня сделать необычный шаг - опубликовать второй ответ. Я надеюсь, что вы найдете это полезным.

Что я собираюсь сделать здесь, это показать вам альтернативную иерархию классов, которая, я надеюсь, вы увидите, лучше подходит для задачи, которую вы хотите решить. Я просто собираюсь сосредоточиться на отношениях между классом Button (в этом заключается фундаментальная проблема) и тем, что я собираюсь назвать ButtonHandler.

Теперь ButtonHandler может быть чем угодно - Button определенно не волнует, что это такое - его просто нужно уведомить, когда кнопка, которой он владеет , нажата или отпущена. Как только вы поймете, что это лежит в основе проблемы, решение становится более очевидным. Конечно, вы можете применить этот подход к своему Sender классу (просто получить его из ButtonHandler и реализовать OnPress и OnRelease), но я оставлю этот бит вам.

Наконец, в качестве небольшого обледенения на пироге мои Button добавили и удалили себя в / из того списка, о котором вы говорили, чтобы их можно было легко опрашивать в каком-то цикле.

Хорошо, поехали. Давайте начнем с нашего ButtonHandler (поскольку это должно быть объявлено перед классом Button), что достаточно просто:

#include <set>
#include <iostream>

class Button;

// Our abstract button handler - derive concrete handlers from this
class ButtonHandler
{
public:
    // Constructor
    ButtonHandler ();

    // Destructor (must be virtual!)
    virtual ~ButtonHandler ();

    virtual void OnPress () = 0;
    virtual void OnRelease () = 0;

private:
    Button *m_button;
};

ОК, там особо не на что смотреть. Обратите внимание на чистые виртуальные методы OnPress и OnRelease, которые существуют для конкретных классов, производных от этого для переопределения, и предварительное объявление класса Button. Нам нужно будет реализовать тела конструктора и деструктора позже, после того как они увидят полное объявление класса Button, поскольку им нужно вызывать методы для него.

Теперь для самого класса Button и связанного с ним ButtonList. Обратите внимание, что, опять же, поскольку он имеет дело только с указателями на Button s, ButtonList не нужно видеть полное объявление класса Button, что здесь очень удобно:

std::set<Button *> ButtonList;

// class Button
class Button
{
public:
    // Constructor
    Button (ButtonHandler *button_handler)
    {
        m_button_handler = button_handler;
        ButtonList.insert (this);
    }

    // Destructor
    ~Button () { ButtonList.erase (this); }

    // Poll the button state
    void Poll ()
    {
        // This would normally check (in hardware) whether button is pressed, but here we just fake it
        m_button_handler->OnPress ();
        m_button_handler->OnRelease ();
    }

private:
    ButtonHandler *m_button_handler;
};

// Poll all the buttons that exist
void PollAllButtons ()
{
    for (auto button : ButtonList) { button->Poll (); }
}

Так что здесь происходит? Ну:

  • Конструктор Button передает указатель на ButtonHander, чтобы он мог при необходимости вызывать его методы OnPress и OnRelease.
  • Волшебство STL (когда доступно!) Делает реализацию ButtonList тривиальной. Он просто использует std::set, что задокументировано здесь (см. Примеры на этой странице и связанных страницах). Кнопки добавляются и удаляются из этого списка по мере их появления.
  • PollAllButtons перебирает все Button *, хранящиеся в ButtonList, и вызывает то, что я выбрал, для каждого из них Poll, что, в свою очередь, вызывает связанные ButtonHandler s OnPress и OnRelease методы по мере необходимости. При этом используется нечто, называемое «циклом в диапазоне», которое, AFAIK, работает со всеми классами контейнеров STL.

Теперь реализация конструктора и деструктора ButtonHandler. Они просто создают и удаляют связанную кнопку (так что Buttonhandler владеет кнопкой):

// ButtonHandler constructor - implementation
ButtonHandler::ButtonHandler ()
{
    m_button = new Button (this);
}

// Buttonhandler destructor - implementation
ButtonHandler::~ButtonHandler () { delete m_button; }

И, наконец, здесь есть конкретный обработчик кнопок и минимальная тестовая программа, чтобы показать вам, что все эти вещи действительно работают. Вы заметите (и это довольно приятно), что это не отличается от предыдущего кода, который я опубликовал (который был слишком умным ):

// An example concrete button handler
class MyButtonHandler : public ButtonHandler
{
public:
    // Constructor
    MyButtonHandler (int handler_id) { m_handler_id = handler_id; }

    void OnPress () override { std::cout << "MyButtonHandler::OnPress (" << m_handler_id << ")" << std::endl; }
    void OnRelease () override { std::cout << "MyButtonHandler::OnRelease (" << m_handler_id << ")" << std::endl; }
    // ...

private:
    int m_handler_id;
};

// Test program
int main ()
{
    MyButtonHandler bh1 (1);
    MyButtonHandler bh2 (2);
    PollAllButtons ();
}

Выход:

MyButtonHandler::OnPress (1)
MyButtonHandler::OnRelease (1)
MyButtonHandler::OnPress (2)
MyButtonHandler::OnRelease (2)

Запустите его на Wandbox .

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

0 голосов
/ 28 июня 2018

Чтобы сделать это, вы должны переопределить f() в MyClass:

class MyClass : public Interface, public Base {
    public:
        void f() {
            Base::f();
        }
        void g() {
            cout << "MyClass::g()" << endl;
        }
};
0 голосов
/ 28 июня 2018

Почему у вас есть функция с именем f в Interface и Base, когда вы впоследствии наследуете от них обоих в MyClass? Вы создали конфликт имен, отсюда и ваши трудности. Итак, на этот раз вы можете сойти с рук с помощью трехстрочного метода. Но в следующий раз? А следующий? Нет, не ходи туда.

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

То, что вы пытаетесь сделать здесь, идет вразрез с духом языка и может привести к неприятностям в будущем, поэтому вам нужно скорректировать свое мышление.

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

Во всяком случае, теперь вы знаете, почему вы получили отрицание (хотя не я). Я надеюсь, что вы найдете это полезным - это хорошо задумано. Мне не нужно было писать это, но опять же, я хотел, я вижу такие вещи на SO и нахожу это болезненным.

0 голосов
/ 28 июня 2018

Класс Base реализует функции в Interface, поэтому он должен быть производным от этого класса. Тогда MyClass наследуется только от Base:

class Base : public Interface {
    public:
        void f() {
            cout << "Base::f()" << endl;
        }
};

class MyClass : public Base {
    public:
        void g() {
            cout << "MyClass::g()" << endl;
        }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...