我可以从不同的基类实现对同一函数的不同实现吗?

Can I implement different implementation to same function from different base classes?

本文关键字:实现 函数 基类 我可以      更新时间:2023-10-16

假设如下:

class A{ virtual void f() = 0; };
class B{ virtual void f() = 0; };

我可以以某种方式执行以下操作吗?

class C : public A, public B
{
  virtual void A::f(){ printf("f() from A"); }
  virtual void B::f(){ printf("f() from B"); }
};

所以现在我可以做到了???

A* pa = new C();
pa->f(); // prints f() from A;
B* pb = (B*)pa;
pb->f(); // prints f() from B;

谢谢!!!

第一个解决方案

这个问题提醒了"立面"设计模式。这应该重写为:

class AC : public A
{ public: virtual void f(){ cout << "f() from A" << endl;}; };
class BC : public B ...
class C : public AC, public BC {};

其中 C 是"立面"。

所以在正确的调用语法应该是这样的:

C* c = new C();
c->AC::f();
c->BC::f();

如果你在AC和BC之间没有任何份额限制,这应该可以完成这项工作,因为它没有被关闭。

第二种解决方案

感谢 Casey(请参阅第一条评论),另一种解决方案是在模板中使用类 C 的前向声明,以允许调用定义后者的方法。

template <typename C>
class AC : public A {
public:
    void f() { static_cast<C&>(*this).f_from_A(); }
};
template <typename C>
class BC : public B { ... };

因此,实现部分可以在同一类中完成。

class C : public AC<C>, public BC<C> {
public:
    void f_from_A() { cout << "f_from_A" << endl; };
    void f_from_B() ...
};

调用部分更干净,因为它不显示任何实现细节并且最接近问题

C* c = new C();
((A*) c) -> f();
((B*) c) -> f();

C 上不再有"默认"f(),并且有可能破坏预期的继承行为,并且更难阅读。

作为参考,以下链接似乎相关:

  1. 从不同的基类重载同名的虚函数。可能吗?
  2. C++同名的虚拟覆盖函数
  3. 派生类通过基类定义函数

第二个链接中的OP问他是否可以像你问的那样做一些事情:

class Impl : public A , public B
{
  public:
     void A::Function () {  cout << "A::Function" << endl; }
     void B::Function () {  cout << "B::Function" << endl; }
};

约翰内斯的回答是:

不能在此处使用限定名称。我你写 void 函数() { ... }您正在覆盖这两个函数。赫伯·萨特展示了它是如何做到的 被解决。

另一种选择是重命名这些函数,因为显然它们 做一些不同的事情(否则我看不到问题 以相同的行为覆盖两者)。

所以一个可能的解决方案(来自第一个链接的James Kanze)看起来像这样:

class RemapA : public A
{
    virtual void fnInA() = 0;
public:
    virtual void fn()
    {
        fnInA();
    }
};
class RemapB : public B
{
    virtual int fnInB() = 0;
public:
    virtual int fn()
    {
        return fnInB();
    }
};
class C : public RemapA, public RemapB
{
    virtual void fnInA() { /* ... */ }
    virtual void fnInB() { /* ... */ }
    //  ...
};

赫伯·萨特(Herb Sutter)的链接基本上说了同样的事情。

要覆盖这两个函数,您需要中间类。C 中的 using 声明选择 A 的函数:

#include <iostream>
struct A{ virtual void f() = 0; };
struct B{ virtual void f() = 0; };
struct IntermediateA : public A {
    virtual void f() override { std::cout << "An"; }
};
struct IntermediateB : public B {
    virtual void f() override { std::cout << "Bn"; }
};

struct C : public IntermediateA, public IntermediateB
{
    using IntermediateA::f;
};
int main() {
    C c;
    B* b = &c;
    c.f();
    b->f();
    return 0;
}

是的。每个基类都有自己的虚拟表。
因此,通过强制转换,编译器将检查要转换为的类型的虚拟表,并获取指向该匹配函数的指针。

class A {public: virtual void f() = 0; };
class B {public: virtual void f() = 0; };
class C : public A, public B
{
public:
    virtual void A::f(){ printf("f() from A"); }
    virtual void B::f(){ printf("f() from B"); }
};

GCC 不支持此功能。只有Visual Studio这样做。

但是,您必须始终使用强制转换或作用域函数调用。
这将返回一个不明确的调用错误:

C* c = new C();
c->f(); //ambiguous call compilation error
This will work:
((B*)c)->f();

编译器显示的结构如下所示:(Visual studio flag: /d1reportSingleClassLayoutC

  class C   size(8):
    +---
    | +--- (base class A)
   0    | | {vfptr}
    | +---
    | +--- (base class B)
   4    | | {vfptr}
    | +---
    +---
  C::$vftable@A@:
    | &C_meta
    |  0
   0    | &C::f
  C::$vftable@B@:
    | -4
   0    | &CCCC::f
  C::f this adjustor: 4
  C::f this adjustor: 0

基本上,您可以看到匹配虚拟表的 2 个单独指针。
所以你可以做:

    C* c= new C();
    B* b = (B*)c;
    b->f(); // calls C::B::f()
    A* a = (A*)c;
    a->f(); // calls C::A::f()
   or even:
    a = (A*)(C*)b; 
    a->f(); // calls C::A::f()

请注意,您需要将大小写b回到派生类才能访问A类型基类的虚拟表。