【问题标题】:Inherit interfaces which share a method name继承共享方法名的接口
【发布时间】:2011-01-01 13:36:30
【问题描述】:

有两个基类具有相同的函数名。我想继承它们,并以不同的方式覆盖每种方法。如何使用单独的声明和定义(而不是在类定义中定义)来做到这一点?

#include <cstdio>

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2
{
public:
    virtual void Name() = 0;
};

class RealClass: public Interface1, public Interface2
{
public:
    virtual void Interface1::Name()
    {
        printf("Interface1 OK?\n");
    }
    virtual void Interface2::Name()
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}   

我未能在 VC8 中将定义移出。我发现 Microsoft 特定关键字 __interface 可以成功完成这项工作,代码如下:

#include <cstdio>

__interface Interface1{
    virtual void Name() = 0;
};

__interface Interface2
{
    virtual void Name() = 0;
};

class RealClass: public Interface1,
                public Interface2
{
public:
    virtual void Interface1::Name();
    virtual void Interface2::Name();
};

void RealClass::Interface1::Name()
{
    printf("Interface1 OK?\n");
}

void RealClass::Interface2::Name()
{
    printf("Interface2 OK?\n");
}

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}  

但是是否有另一种方法可以在其他编译器中使用更通用的方法?

【问题讨论】:

  • 你为什么要这个?看来您正在破坏虚拟功能的目的;您只是在复制非虚拟语义。你有__interface的参考吗?我查了一下,它似乎没有做任何必要的事情。
  • Potatoswatter - 你可能需要这样做的原因是你可能有一个类,它通过从两个接口继承来汇集两个不同的概念。类的用户很可能通过接口使用它,两个不同接口的用户互不了解或不关心。该类将这些结合在一起,需要区分对一个接口的调用和对另一个接口的调用。您绝不会违背虚函数的目的。 Gohan - 如果 __inteface 在这里有所作为,我会感到惊讶。
  • @Len 不同的是,如果你想直接把implement 部分移出,__interface 关键字会使其工作,而使用标准类或结构只包含纯虚函数会得到编译器的错误。
  • @Potatoswatter “你只是在复制非虚拟语义。”“复制非虚拟语义”到底是什么意思?
  • 那么,Interface1::Name 是一个un限定名称吗?

标签: c++ visual-c++ gcc multiple-inheritance


【解决方案1】:

这个问题并不经常出现。我熟悉的解决方案是由 Doug McIlroy 设计的,并出现在 Bjarne Stroustrup 的书中(在C++ 的设计与演变第 12.8 节和 C++ 编程语言第 25.6 节中都有介绍)。根据 Design & Evolution 中的讨论,有人提议优雅地处理这种特定情况,但被拒绝了,因为“这种名称冲突不太可能变得普遍到足以保证单独的语言功能”并且“不太可能成为新手的日常工作。”

您不仅需要通过指向基类的指针调用Name(),还需要在对派生类进行操作时说哪个 Name()。该解决方案增加了一些间接性:

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2{
public:
    virtual void Name() = 0;
};

class Interface1_helper : public Interface1{
public:
    virtual void I1_Name() = 0;
    void Name() override
    {
        I1_Name();
    }
};

class Interface2_helper : public Interface2{
public:
    virtual void I2_Name() = 0;
    void Name() override
    {
        I2_Name();
    }
};

class RealClass: public Interface1_helper, public Interface2_helper{
public:
    void I1_Name() override
    {
        printf("Interface1 OK?\n");
    }
    void I2_Name() override
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    RealClass rc;
    Interface1* i1 = &rc;
    Interface2* i2 = &rc;
    i1->Name();
    i2->Name();
    rc.I1_Name();
    rc.I2_Name();
}

不漂亮,但决定是不需要经常这样做。

【讨论】:

  • 您的思维和打字速度非常快;v)。 +1
  • 您提供了提案的来源真是太棒了,虽然不太清楚您为什么不将提案本身的内容包含在实际答案中,至少作为一个“有趣的事实”,但很简短.为了填写自己,我不得不在网上寻找这本书。相当有趣的阅读。对于未来的读者:被拒绝的提案引入了以下语法来“重命名”已实现接口的方法:virtual void Name1() = Interface1::Name;Cheers
  • @Tomalla 将是未来 C++ 标准的一个很好的补充。 :)
【解决方案2】:

你不能单独覆盖它们,你必须同时覆盖它们:

struct Interface1 {
  virtual void Name() = 0;
};

struct Interface2 {
  virtual void Name() = 0;
};

struct RealClass : Interface1, Interface2 {
  virtual void Name();
};
// and move it out of the class definition just like any other method:
void RealClass::Name() {
  printf("Interface1 OK?\n");
  printf("Interface2 OK?\n");
}

您可以使用中间基类模拟单独的覆盖:

struct RealClass1 : Interface1 {
  virtual void Name() {
    printf("Interface1 OK?\n");
  }
};

struct RealClass2 : Interface2 {
  virtual void Name() {
    printf("Interface2 OK?\n");
  }
};

struct RealClass : RealClass1, RealClass2 {
  virtual void Name() {
    // you must still decide what to do here, which is likely calling both:
    RealClass1::Name();
    RealClass2::Name();

    // or doing something else entirely

    // but note: this is the function which will be called in all cases
    // of *virtual dispatch* (for instances of this class), as it is the
    // final overrider, the above separate definition is merely
    // code-organization convenience
  }
};

此外,您使用 reinterpret_cast 不正确,您应该:

int main() {
  RealClass rc; // no need for dynamic allocation in this example

  Interface1& one = rc;
  one.Name();

  Interface2& two = dynamic_cast<Interface2&>(one);
  two.Name();

  return 0;
}

这里是用CRTP 重写的,可能是你想要的(或不是):

template<class Derived>
struct RealClass1 : Interface1 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface1 for %s\n", self.name.c_str());
  }
#undef self
};

template<class Derived>
struct RealClass2 : Interface2 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface2 for %s\n", self.name.c_str());
  }
#undef self
};

struct RealClass : RealClass1<RealClass>, RealClass2<RealClass> {
  std::string name;
  RealClass() : name("real code would have members you need to access") {}
};

但请注意,您现在不能在 RealClass 上调用 Name(使用虚拟调度,例如 rc.Name()),您必须首先选择一个基。 self 宏是一种清理 CRTP 强制转换的简单方法(通常成员访问在 CRTP 基础中更为常见),但它可以是 improved。在我的 other answers 之一中有一个关于虚拟调度的简短讨论,但如果有人有链接,肯定会更好。

【讨论】:

  • 您的中间基类似乎没有做任何事情。您仍然可以像 RealClass1::Name 一样拨打 Interface1::Name
  • ...我的意思是,如果它是为 RealClass1 实现的,你可以。
  • 不能调用rc.Interface1::Name(),这是一个没有实现的纯虚函数。
  • 另外请注意,所有这些代码都是标准 C++ 并且应该可以与任何 C++ 编译器一起使用,尽管我实际上只测试了 CRTP 案例并仅证明了其余的正确。
  • 在我的第二条评论中谈到了这一点 ;v) 。如果他可以添加中间类,他可能可以将实现添加到他已经拥有的类中。无论如何,它只是破坏了 vtable 查找。
【解决方案3】:

过去我不得不做这样的事情,但在我的情况下,我需要从一个接口继承两次,并且能够区分对每个接口进行的调用,我使用了一个模板填充程序来帮助我。 ..

类似这样的:

template<class id>
class InterfaceHelper : public MyInterface
{
    public : 

       virtual void Name() 
       {
          Name(id);
       }

       virtual void Name(
          const size_t id) = 0;  
}

然后您从InterfaceHelper 派生两次,而不是从MyInterface 派生两次,并为每个基类指定不同的id。然后,您可以通过转换为正确的InterfaceHelper 来独立分发两个接口。

你可以做一些稍微复杂一些的事情;

class InterfaceHelperBase
{
    public : 

       virtual void Name(
          const size_t id) = 0;  
}


class InterfaceHelper1 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(1);
       }
}

class InterfaceHelper2 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(2);
       }
}

class MyClass : public InterfaceHelper1, public InterfaceHelper2
{
    public :

      virtual void Name(
          const size_t id)
      {
          if (id == 1) 
          {
              printf("Interface 1 OK?");
          }
          else if (id == 2) 
          {
              printf("Interface 2 OK?");
          }
      }  
}

注意上面没有看到编译器...

【讨论】:

  • 无需使用编译器即可看到Name(2); 不能是递归调用,因为此函数Name 不带参数!
  • 这不是递归调用。 Name() 的实际实现调用包含 Id 的版本,以便您可以知道在基础对象中调用了哪个版本的 Name()。然后,您将辅助对象传递给碰巧需要实际接口的任何人。
  • 但正如所写,它只能是递归调用。例如,您可以使用范围解析运算符来调用另一个 Name 函数。
【解决方案4】:
class BaseX
{
public:
    virtual void fun()
    {
        cout << "BaseX::fun\n";
    }
};

class BaseY
{
public:
    virtual void fun()
    {
        cout << "BaseY::fun\n";
    }
};


class DerivedX : protected BaseX
{
public:
    virtual void funX()
    {
        BaseX::fun();
    }
};

class DerivedY : protected BaseY
{
public:
    virtual void funY()
    {
        BaseY::fun();
    }
};


class DerivedXY : public DerivedX, public DerivedY
{

};

【讨论】:

    【解决方案5】:

    还有另外两个相关的问题询问几乎(但不完全)相同的事情:

    Picking from inherited shared method names。如果你想让 rc.name() 调用 ic1->name() ic2->name()。

    Overriding shared method names from (templated) base classes。与您接受的解决方案相比,这具有更简单的语法和更少的代码,但 不允许 允许从派生类访问函数。或多或少,除非您需要能够从 rc 调用 name_i1(),否则您不需要使用 InterfaceHelper 之类的东西。

    【讨论】:

      猜你喜欢
      • 2010-11-09
      • 1970-01-01
      • 2023-04-02
      • 2018-05-26
      • 1970-01-01
      • 2016-03-15
      • 2011-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多