Nice programing

메소드 이름을 공유하는 인터페이스 상속

nicepro 2020. 12. 30. 20:25
반응형

메소드 이름을 공유하는 인터페이스 상속


동일한 함수 이름을 가진 두 개의 기본 클래스가 있습니다. 나는 둘 다 상속하고 각 방법을 다르게 타고 싶어합니다. 클래스 정의에서 정의하는 대신 별도의 선언과 정의로 어떻게 할 수 있습니까?

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

하지만 다른 컴파일러에서 작동하는 더 일반적인 작업을 수행하는 또 다른 방법이 있습니까?


이 문제는 자주 발생하지 않습니다. 내가 익숙한 솔루션은 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();
}

예쁘지는 않지만 결정은 자주 필요하지 않다는 것입니다.


별도로 재정의 할 수 없으며 한 번에 모두 재정의해야합니다.

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 기반에서 훨씬 더 일반적 임) 개선 할 수 있습니다 . 다른 답변 중 하나에 가상 디스패치에 대한 간단한 토론이 있지만 누군가 링크가 있으면 분명히 더 나은 것입니다.


과거에는 이와 같은 작업을 수행해야했지만, 제 경우에는 하나의 인터페이스에서 두 번 상속하고 각각에 대한 호출을 구분할 수 있어야했지만 템플릿 shim을 사용하여 도움이되었습니다.

이 같은:

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?");
          }
      }  
}

위의 컴파일러는 보지 못했습니다.


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
{

};

거의 (완전하지는 않음) 동일한 것을 묻는 두 가지 다른 관련 질문이 있습니다.

상속 된 공유 메서드 이름에서 선택 . rc.name ()을 사용하려면 ic1-> name () 또는 ic2-> name ()을 호출하십시오 .

Overriding shared method names from (templated) base classes. This has simpler syntax and less code that your accepted solution, but does not allow for access to the functions from the derived class. More or less, unless you need to be able to call name_i1() from an rc, you don't need to use things like InterfaceHelper.

ReferenceURL : https://stackoverflow.com/questions/2004820/inherit-interfaces-which-share-a-method-name

반응형