C++ 多重继承:使用基类 A 的实现实现基类 B 的抽象方法

C++ Multiple Inheritance: Using base class A's implementation for abstract method of base class B

本文关键字:实现 基类 多重继承 抽象方法 C++      更新时间:2023-10-16

我有一个抽象类Interface,它声明了f()g()方法。

MyClass实现了Interface::g()MyClass继承了Base,后者实现了自己的f()

如何使Base::f()成为MyClassInterface::f()的实现?

如果可能的话,我不想让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();
}

这不起作用,因为fMyClass中仍然是一个纯虚拟函数。

在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()方法与按钮无关,因此从Sender继承Button是没有意义的
还有其他类可以实现Button,但它们并不都是Sender

也许我必须彻底改变结构,但目前我真的看不到更好的解决方案。

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();
}

要实现此功能,必须覆盖MyClass:中的f()

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

为什么在InterfaceBase中都有一个名为f的函数,而随后在MyClass中从这两个函数派生?你在那里制造了一场名字冲突,因此你遇到了困难。好的,所以这次你可以用三线法逃脱惩罚。但下次呢?下一个呢?不,不要去那里。

C++不是Java,它没有接口。相反,正如您所发现的,从抽象基类派生的具体类(或类链-使用单一继承)需要实现该类声明的所有纯虚拟方法。

你在这里试图做的事情违背了语言的精神,很可能会给你带来麻烦,所以你需要调整你的思维。

多重继承,尤其是当在一个或多个基类中声明相同的方法或数据成员名称时,通常是一个糟糕的设计选择,而且更常见的症状是类层次结构设计中固有的错误,所以我的建议是重新审视这一点-你会很高兴你做到了。

无论如何,现在你知道为什么你否决了(尽管不是我)。我希望你觉得这很有用——这是出于好意。我不必写这篇文章,但话说回来,我想写,我在SO上经常看到这种事情,我觉得很痛苦。

感谢OP发布了一个更具体的例子。这促使我采取了不同寻常的步骤,发布了第二个答案。我希望你觉得它有用。

我在这里要做的是向您展示一个替代类层次结构,我希望您能看到它更适合您想要解决的问题。我将重点讨论类Button(这是根本问题所在)和我将称之为ButtonHandler的东西之间的关系。

现在,ButtonHandler可以是任何东西——Button当然不在乎它是什么——它只需要在按下或释放它拥有的按钮时得到通知。一旦你意识到这是问题的核心,解决方案就会变得更加明显。当然,您可以将这种方法应用于Sender类(只需从ButtonHandler派生它并实现OnPressOnRelease),但我将把这一点留给您。

最后,锦上添花的是,我让我的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;
};

好吧,没什么可看的。请注意纯虚拟方法OnPressOnRelease,它们用于覆盖从该方法派生的具体类,以及类Button的前向声明。我们稍后需要在构造函数和析构函数体看到类Button的完整声明之后实现它们,因为它们需要调用上面的方法

现在针对类Button本身以及相关联的ButtonList。再次注意,因为它只处理指向Buttons的指针,所以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的指针,以便它可以在适当的时候调用其OnPressOnRelease方法
  • STL的魔力(如果可用!)使ButtonList的实现变得微不足道。它只是使用std::set,此处对此进行了说明(请参阅该页面和链接页面上的示例)。按钮来来去去时会将自己添加到该列表中或从该列表中删除
  • PollAllButtons遍历存储在ButtonList中的所有Button *,并为每一个调用我选择的调用Poll的内容,这反过来又适当地调用相关的ButtonHandlerOnPressOnRelease方法。这使用了一种叫做"ranged for loop"的东西,它可以与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上运行。

好了。与你的非常不同(因为你走错了路,从此没有回头路)。我建议你带着它跑,祝你好运。

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;
}
};