64

同じ関数名を持つ2つの基本クラスがあります。私はそれらの両方を継承し、それぞれの方法を異なる方法で乗り越えたいと思っています。(クラス定義で定義する代わりに)別々の宣言と定義でそれを行うにはどうすればよいですか?

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

しかし、他のコンパイラで機能する、より一般的なこれを行う別の方法はありますか?

4

5 に答える 5

80

この問題はあまり発生しません。私がよく知っている解決策は、Doug McIlroy によって設計され、Bjarne Stroustrup の書籍 ( Design & Evolution of C++セクション 12.8 とThe C++ Programming Languageセクション 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();
}

きれいではありませんが、頻繁には必要ないという決定が下されました。

于 2010-01-05T09:44:25.243 に答える
8

それらを個別にオーバーライドすることはできません。一度に両方をオーバーライドする必要があります。

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 ベースでより一般的です)、改善することができます。私の他の回答の1つに仮想ディスパッチの簡単な議論がありますが、誰かがリンクを持っていれば、確かにより良いものです。

于 2010-01-05T08:42:07.607 に答える
6

私は過去にこのようなことをしなければなりませんでしたが、私の場合は1つのインターフェースから2回継承し、それぞれで行われた呼び出しを区別できるようにする必要がありましたが、テンプレートシムを使用して助けました...

このようなもの:

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

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

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

InterfaceHelper次に、 2 回ではなく 2 回から派生させ、基本クラスごとMyInterfaceに異なる を指定します。id次に、正しい にキャストすることで、2 つのインターフェイスを個別に渡すことができます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?");
          }
      }  
}

上記はコンパイラを見ていないことに注意してください...

于 2010-01-05T09:42:35.200 に答える
3
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
{

};
于 2010-01-06T09:17:38.977 に答える
2

ほぼ(完全ではない)同じことを尋ねる他の2つの関連する質問があります。

継承された共有メソッド名からの選択。rc.name() を呼び出したい場合は、ic1->name()またはic2->name() を呼び出します。

(テンプレート化された) 基本クラスからの共有メソッド名のオーバーライド。これは、受け入れられたソリューションよりも構文が単純でコードが少ないですが、派生クラスから関数にアクセスすることはできません。多かれ少なかれ、rc から name_i1() を呼び出せるようにする必要がない限り、InterfaceHelper などを使用する必要はありません。

于 2010-01-06T01:20:55.660 に答える