【问题标题】:Interfaces in c++C++ 中的接口
【发布时间】:2009-04-30 12:02:14
【问题描述】:

我想在 c++ 中使用接口,例如在 java 或 c# 中。我决定使用具有多重继承的纯抽象类,但是当我专门化接口时出现了严重错误:

class Interface
{
public:
  virtual int method() = 0;
};

// Default implementation.
class Base: virtual public Interface
{
public:
  virtual int method() {return 27;}
};

// specialized interface
class Interface2: public Interface
{
public:
  virtual int method() = 0;
// some other methods here
};


// concrete class - not specialised - OK
class Class: public virtual Interface, public virtual Base
{
};

// concrete class - specialised
class Class2: public Interface2, public Base
{
};



int main()
{
  Class c;
  Class2 c2;
return 0;
}

警告 1 警告 C4250: 'Class' : 通过支配 30 继承 'Base::Base::method'

错误 2 错误 C2259: 'Class2' : 无法实例化抽象类 42

这样做的正确方法是什么?

【问题讨论】:

  • 为什么不对 Class2 使用虚拟继承?
  • 另一个问题是您为什么要尝试用 C++ 重新实现 Java?
  • 你到底想做什么?你想要mixins,一个基类中的方法可以调用另一个基类中的方法,还是只是“常规”多重继承?如果是后者,为什么要使用虚拟继承,为什么要同时继承Interface和Base(后者就足够了)?
  • 要明确:请准确描述您对 Class 和 Class2 的期望行为(例如,调用 method() 时应该发生什么;可以使用对 Interface2 的指针/引用来指向实例) .
  • 如果方法没有在 Class2 或 Class 中重新实现(在这种情况下不是) Base::method() 将被调用。否则将调用重新实现。有一个具有通用基础哑实现的接口层次结构。

标签: c++


【解决方案1】:

Class2继承自一个抽象类(Interface2),但没有实现纯虚方法,所以它仍然是一个抽象类。

【讨论】:

  • 方法在Base中实现。 Base 带来了默认实现。 Interface2 是专用接口。想象一下 Interface = Protocol,Interface= TCP/IP 协议,Base - 包含支持方法。类是一个 http 服务器。
  • 可以,但是在Interface2中没有实现。如果他们做不同的事情,那么他们可能不应该有相同的名字。
  • 接口从不实现任何东西。这只是声明某些方法可用。例如接口可以做method()、method2()和method3()。 Interface2 带来了method4() 和method5()。 Interface 和 Interface2 可能有许多实现。 Base 通常包含可以被整个层次结构重用的琐碎操作。
  • @danatel,Tom 表示 Class2 不会自动“使用”实现 Base::method() 作为 Interface2::method() 的实现,因此您需要一些额外的语法来实现它影响。这可能是为了避免意外的接口......对不起,纯抽象类......由于从另一个具体类继承引起的随机名称冲突而导致的方法实现。
  • 同意。到目前为止,这似乎在 C++ 中是不可能做到的。请参阅 Pieters 答案和 j_random_hackers cmets。
【解决方案2】:

嘿嘿,这个问题在我脑海深处的某个地方发痒。我不能完全理解它,但我认为这与定义接口层次结构然后继承接口和实现有关。然后,您不必通过将调用转发到基类来实现所有功能。我想。

我认为这个简单的例子展示了同样的东西,但可能更容易理解,因为它使用的东西可以很容易地可视化:(请原谅结构的懒惰)

#include <iostream>
using namespace std;

struct Vehicle
{
    virtual void Drive() = 0;
};

struct VehicleImp : virtual public Vehicle
{
    virtual void Drive() 
    {
        cout << "VehicleImp::Drive\n";
    }
};

struct Tank : virtual public Vehicle
{ 
    virtual void RotateTurret() = 0;
};

struct TankImp : public Tank, public VehicleImp
{
    virtual void RotateTurret() 
    {
        cout << "TankImp::RotateTurret\n";
    }
    // Could override Drive if we wanted
};

int _tmain(int argc, _TCHAR* argv[])
{
    TankImp myTank;
    myTank.Drive();         // VehicleImp::Drive
    myTank.RotateTurret();  // TankImp::RotateTurret
    return 0;
}

TankImp 基本上继承了 Tank 接口和 Vehicle 实现。

现在,我很确定这是 OO 圈子中众所周知且可以接受的事情(但我不知道它是否有一个花哨的名字),所以在这种情况下,可怕的钻石事情是可以的,你可以安全地抑制优势警告,因为这是您在这种情况下想要发生的事情。

希望能以某种方式帮助您指明正确的方向!

顺便说一句,您的代码没有编译,因为您没有在 Class2 中实现纯虚拟“方法”。

编辑:

好的,我想我现在更好地理解了您的问题,并且我认为错误出在 Interface2 中。尝试将其更改为:

// specialized interface
class Interface2: public virtual Interface // ADDED VIRTUAL
{
public:
    //virtual int method() = 0;   COMMENTED THIS OUT
    // some other methods here
};

Interface2 不应该有方法的纯虚定义,因为它已经在 Interface 中。

Interface 的继承必须是虚拟的,否则当你从 Interface2 和 Class2 中的 Base 派生时,你会与 Base::method 产生歧义。

现在您应该会发现它会编译,可能带有优势警告,当您调用 c2.method() 时,您会得到 27。

【讨论】:

  • 是的,关键是我不想实现class2中的方法。我想继承基本实现。不幸的是,您的示例与我的情况无关。
  • 宾果游戏!这就是我一直在寻找的答案!结论:使用虚拟继承确实允许在C++中“混合”方法。 danatel:请将此标记为已接受的答案。
  • 总结一下:问题在于Class2的定义中有两个不同的Interface子对象——一个来自Base,一个来自Interface2。使 Interface2 虚拟继承 Interface 将这两个子对象合并为一个,消除了调用“Interface::method()”的歧义。
【解决方案3】:

基于此评论

如果方法没有在 Class2 或 Class 中重新实现(它不在 这种情况下) Base::method() 将被调用。否则重新实现 将被调用。有一个具有共同基础的接口层次结构 愚蠢的实施。 – danatel 16 分钟前

这不是你得到的,你没有共同的基础,你有

Interface -> Interface2 -> Class2
Interface -> Base -> Class2

接口在派生树中没有“合并”,interface2 不会从接口虚拟继承,因此它会有自己的接口超类。 就像纯虚method()在Class2中存在两次,一次通过Class实现,一次不实现。

即使你已经虚拟继承,公共基础(接口)仍然没有实现

如果 Base 包含应该在整个层次结构中可用的琐碎操作,那么为什么不将 Base 作为您的起点呢? (即使仍然是纯虚拟的实现)。

如果这只是一个非常简单的示例以使问题简短,那么Bridge Pattern 之类的内容可能更有用。但是如果不了解更多信息,很难进一步指导您。

【讨论】:

  • 感谢您的分析。它描述了为什么我的方法在 C++ 中可能不可行。我喜欢“合并”这个词,它准确地描述了我想要的。我希望虚拟多重继承能为我做到这一点。最后,我可能会像您建议的那样以 Base 作为根的非抽象层次结构结束,失去纯抽象接口的所有优点。
  • 好点,也许在这种情况下 Base 可以成为根类,但在某些情况下这不是一个好主意——例如if Base 引入了成员变量。在这种情况下,使 Base 成为根将永远将派生类与携带这些成员联系起来,即使它们从未被使用过。请记住,Base 只包含通常很方便的东西——但它对于实现接口并不是必需的
  • 注意:markh44 的出色回答表明,通过使用 virtual 继承,方法 实际上可以 合并到 C++ 中。
【解决方案4】:

如果您可能使用接口或基指针删除,您还应该考虑在接口中定义虚拟析构函数。

如果没有虚拟析构函数,你会遇到如下问题:

Base *b = new Class2();
delete b;

【讨论】:

  • ++1 这会让你各种头疼。
  • 感谢您的评论。我知道虚拟析构函数的存在,我只是想让这个例子简短一些。
  • 很高兴听到这个消息。只要确保它会在人们不知道的情况下绊倒他们。
【解决方案5】:

关于Class你需要做的就是从Base派生Class——它实现Interface的事实是隐含的,事实上,这是不可避免的: p>

class Class: public Base  // virtual inheritance is unnecessary here
{
};

Class 将根据需要从Base 继承method()

关于Class2

免责声明:前面的否定结果

根据您对Tom's answer 的评论,我认为我有Class2 的答案:

// concrete class - specialised
class Class2: public Interface2, public Base
{
public:
    using Base::method;    // "Imports" all members named "method" from Base
};

但实际上,这是行不通的。对 C++ 标准的抱怨表明 第 7.3.3 节第 14 段解释说,using 不能用于解决对继承成员的模糊访问:

... [注意:因为 using-declaration 指定基类成员(而不是成员子对象或基类子对象的成员函数),所以 using-declaration 不能用于解决继承的成员歧义。 ...]

似乎在Class2 中获得所需行为的唯一方法是手动转发该方法:

// concrete class - specialised
class Class2: public Interface2, public Base
{
public:
     virtual int method() { return Base::method(); }
};

关于virtual 继承:需要它来声明Class,但您可能确实需要它对于Interface2 的声明,以确保Class2 只有一个Interface 类型的子对象——就目前而言,每个Class2 对象都有两个这种类型的子对象。 (尽管如果Interface 实际上是一个纯接口,缺少成员变量,这不会引起问题。)如果有帮助,请画一个图:每次出现没有关键字virtual 的基类时,它都会显示为不同的目的;所有带有关键字virtual 的基类都被压缩成一个对象。

[更新:markh44's excellent answer 表明上述方法(使Interface2Interface 继承虚拟)实际上将允许Class2 自动继承method() 来自Base!问题解决了!]

【讨论】:

  • 谢谢。类总是被编译。我添加了 Class 只是为了表明,在 C++ 中,抽象接口在直接使用接口时有效。当存在接口层次结构时,基方法不会从第二层次层次结构中合并。
  • 不客气。顺便说一句,您的评论很好地总结了这些问题。
【解决方案6】:

This answer 在另一个论坛中似乎可以解决您提到的确切问题。

【讨论】:

  • 在我的例子中,A 和 B 具有相同的公共基础。 C 直接继承自 B,间接继承自 D,后者继承自 A,具有与 A 相同的抽象接口。
【解决方案7】:

一般来说,你应该避免菱形继承模式:

            Interface
            /      \
          Base   Interface2
            \      /
             Class2

如果你不小心的话,这会导致你在路上遇到各种各样的悲伤。模棱两可会咬你。

在您的特定实例中,Interface2 不需要从 Interface 继承。 Interface2 不需要指定“方法”,因为它是抽象的。删除 Interface 和 Interface2 之间的继承以打破菱形。那么你的层次结构是这样的:

        Interface                Interface  Interface2
           |                          |       |
          Base                      Base      |
           |                           \      /
         Class                          Class2

你的实现看起来像:

// concrete class - not specialised - OK
class Class: public Base
{
};

// concrete class - specialised
class Class2: public Base, public Interface2
{
    virtual int method() {return 35;}

    virtual void Inteface2Method { ... }
};

【讨论】:

  • 好吧,我同意避免 DDD 通常是个好主意,但是如果 Interface2 真的是 Interface 的超集(超接口)呢?你真的是说在 C++ 中没有办法表示这种关系吗?
猜你喜欢
  • 2011-04-20
  • 1970-01-01
  • 2017-06-30
  • 2011-04-10
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 2013-04-11
相关资源
最近更新 更多