c ++中的接口

时间:2009-04-30 12:02:15

标签: c++

我想在java或c#中使用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

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

7 个答案:

答案 0 :(得分:5)

Class2继承自抽象类(Interface2),但不实现纯虚方法,因此它仍然是一个抽象类。

答案 1 :(得分:4)

基于此评论

If the method is not reimplemented in Class2 or Class (it is not in 
this case) Base::method() will be called. Otherwise the reimplementation 
will be called. There is an interface hierarchy with a common base 
dumb implementation. 

– danatel 16 mins ago

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

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

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

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

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

如果这只是一个简单的例子来解决问题,那么像Bridge Pattern这样的东西可能更有用。但是如果不了解更多信息,很难引导你。

答案 2 :(得分:4)

嘿嘿嘿,这个问题痒痒地埋藏在我脑海深处的某个地方。我不能完全指责它,但我认为这与定义一个接口heirarchy然后继承接口和一个实现有关。然后,您可以避免通过将调用转发到基类来实现所有功能。我想。

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

#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圈中众所周知且可以接受的东西(但我不知道它是否有一个奇特的名字),所以在这种情况下可怕的钻石是好的,你可以安全地压制支配警告,因为这是你想要在这种情况下发生的事情。

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

BTW,你的代码没有编译,因为你没有在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的继承需要是虚拟的,否则当你从Class2中的Interface2和Base派生时,你会对Base ::方法产生歧义。

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

答案 3 :(得分:3)

关于Class您需要做的就是从Class派生Base - 隐含了它实现Interface的事实,事实上,不可避免:

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

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

关于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虚拟地继承 )实际上允许{{1从Class2自动继承method()的实现!问题解决了!]

答案 4 :(得分:3)

如果您可能正在使用Interface或Base指针进行删除,还应该查看在接口中定义虚拟析构函数。

如果您执行以下操作,则无需虚拟析构函数,您将遇到问题:

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

答案 5 :(得分:2)

This answer在另一个论坛上似乎解决了你提到的确切问题。

答案 6 :(得分:1)

一般来说,你应该避免使用钻石入侵模式:

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