program story

C ++에서 인터페이스를 어떻게 선언합니까?

inputbox 2020. 9. 29. 07:44
반응형

C ++에서 인터페이스를 어떻게 선언합니까?


인터페이스를 나타내는 클래스를 어떻게 설정합니까? 이것은 추상 기본 클래스입니까?


bradtgmurray 의 답변을 확장하려면 가상 소멸자를 추가하여 인터페이스의 순수 가상 메서드 목록에 한 가지 예외를 만들 수 있습니다. 이를 통해 구체적인 파생 클래스를 노출하지 않고도 포인터 소유권을 다른 당사자에게 전달할 수 있습니다. 인터페이스에 구체적인 멤버가 없기 때문에 소멸자는 아무것도 할 필요가 없습니다. 기능을 가상과 인라인으로 정의하는 것은 모순되는 것처럼 보일 수 있지만 저를 믿으십시오. 그렇지 않습니다.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

가상 소멸자에 대한 본문을 포함 할 필요가 없습니다. 일부 컴파일러는 빈 소멸자를 최적화하는 데 문제가 있으며 기본값을 사용하는 것이 좋습니다.


순수 가상 메서드로 클래스를 만듭니다. 해당 가상 메서드를 재정의하는 다른 클래스를 만들어 인터페이스를 사용합니다.

순수 가상 메서드는 가상으로 정의되고 0에 할당 된 클래스 메서드입니다.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

C # / Java의 추상 기본 클래스 외에 특수 인터페이스 유형 범주가있는 전체 이유는 C # / Java 가 다중 상속을 지원하지 않기 때문입니다.

C ++는 다중 상속을 지원하므로 특별한 유형이 필요하지 않습니다. 비추 상 (순수 가상) 메서드가없는 추상 기본 클래스는 기능적으로 C # / Java 인터페이스와 동일합니다.


C ++에는 "인터페이스"라는 개념 자체가 없습니다. AFAIK, 인터페이스는 다중 상속 부족을 해결하기 위해 Java에서 처음 도입되었습니다. 이 개념은 매우 유용한 것으로 밝혀졌으며 추상 기본 클래스를 사용하여 C ++에서 동일한 효과를 얻을 수 있습니다.

추상 기본 클래스는 최소한 하나의 멤버 함수 (Java 용어의 메서드)가 다음 구문을 사용하여 선언 된 순수 가상 함수 인 클래스입니다.

class A
{
  virtual void foo() = 0;
};

추상 기본 클래스는 인스턴스화 할 수 없습니다. 즉, 클래스 A의 개체를 선언 할 수 없습니다. A에서만 클래스를 파생 할 수 있지만 구현을 제공하지 않는 파생 클래스 foo()도 추상이됩니다. 추상이되는 것을 중지하려면 파생 클래스가 상속하는 모든 순수 가상 함수에 대한 구현을 제공해야합니다.

추상 기본 클래스는 순수 가상이 아닌 데이터 멤버와 멤버 함수를 포함 할 수 있으므로 인터페이스 이상이 될 수 있습니다. 인터페이스와 동등한 것은 순수한 가상 함수 만있는 데이터가없는 추상 기본 클래스입니다.

그리고 Mark Ransom이 지적했듯이 추상 기본 클래스는 모든 기본 클래스와 마찬가지로 가상 소멸자를 제공해야합니다.


테스트 할 수있는 한 가상 소멸자를 추가하는 것이 매우 중요합니다. 로 생성 new되고 파괴 된 객체를 사용 하고 delete있습니다.

인터페이스에 가상 소멸자를 추가하지 않으면 상속 된 클래스의 소멸자가 호출되지 않습니다.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

을 사용하지 않고 이전 코드를 실행하면 virtual ~IBase() {};소멸자 Tester::~Tester()가 호출되지 않는 것을 볼 수 있습니다.


내 대답은 기본적으로 다른 것과 동일하지만 다른 두 가지 중요한 일이 있다고 생각합니다.

  1. 인터페이스에서 가상 소멸자를 선언하거나 누군가 유형의 개체를 삭제하려고 할 때 정의되지 않은 동작을 방지하기 위해 보호 된 비가 상 소멸자를 만듭니다 IDemo.

  2. 가상 상속을 사용하여 다중 상속 문제를 방지합니다. (인터페이스를 사용할 때 더 자주 다중 상속이 있습니다.)

다른 답변과 마찬가지로 :

  • 순수 가상 메서드로 클래스를 만듭니다.
  • 해당 가상 메서드를 재정의하는 다른 클래스를 만들어 인터페이스를 사용합니다.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    또는

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    

C ++ 11에서는 상속을 모두 쉽게 피할 수 있습니다.

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

이 경우 인터페이스에는 참조 의미가 있습니다. 즉, 객체가 인터페이스보다 오래 지속되는지 확인해야합니다 (값 의미를 가진 인터페이스를 만들 수도 있음).

이러한 유형의 인터페이스에는 장단점이 있습니다.

마지막으로, 상속은 복잡한 소프트웨어 설계에서 모든 악의 근원입니다. 에서 숀 부모의 값 의미와 다형성을 개념 기반 (추천,이 기술의 더 나은 버전이 설명되어 있습니다) 다음과 같은 경우가 연구되고있다 :

MyShape인터페이스를 사용하여 모양을 다형 적으로 처리하는 응용 프로그램이 있다고 가정 해 보겠습니다 .

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

응용 프로그램에서 YourShape인터페이스를 사용하여 다른 모양으로 동일한 작업을 수행합니다 .

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

이제 응용 프로그램에서 개발 한 일부 모양을 사용하고 싶다고 가정 해 보겠습니다. 개념적으로 셰이프는 동일한 인터페이스를 갖지만 내 셰이프가 애플리케이션에서 작동하도록하려면 다음과 같이 셰이프를 확장해야합니다.

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

첫째, 내 모양을 수정하는 것이 전혀 불가능할 수 있습니다. 또한 다중 상속은 스파게티 코드로 이어집니다 ( TheirShape인터페이스를 사용하는 세 번째 프로젝트가 들어 온다고 상상해보십시오 ... 그리기 함수도 호출하면 my_draw어떻게 될까요?).

업데이트 : 비상 속 기반 다형성에 대한 몇 가지 새로운 참조가 있습니다.


위의 모든 좋은 답변. 명심해야 할 한 가지 추가 사항은 순수 가상 소멸자를 가질 수도 있다는 것입니다. 유일한 차이점은 여전히이를 구현해야한다는 것입니다.

혼란스러워?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

이 작업을 수행하려는 주된 이유는 내가 가지고있는 것처럼 인터페이스 메서드를 제공하고 싶지만 재정의하는 것은 선택 사항 인 경우입니다.

클래스를 만들려면 인터페이스 클래스에 순수 가상 메서드가 필요하지만 모든 가상 메서드에는 기본 구현이 있으므로 순수 가상으로 만드는 유일한 방법은 소멸자입니다.

파생 클래스에서 소멸자를 다시 구현하는 것은 전혀 큰 문제가 아닙니다. 저는 항상 파생 클래스에서 가상이든 아니든 소멸자를 다시 구현합니다.


Microsoft의 C ++ 컴파일러를 사용하는 경우 다음을 수행 할 수 있습니다.

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

I like this approach because it results in a lot smaller interface code and the generated code size can be significantly smaller. The use of novtable removes all reference to the vtable pointer in that class, so you can never instantiate it directly. See the documentation here - novtable.


A little addition to what's written up there:

First, make sure your destructor is also pure virtual

Second, you may want to inherit virtually (rather than normally) when you do implement, just for good measures.


You can also consider contract classes implemented with the NVI (Non Virtual Interface Pattern). For instance:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

I'm still new in C++ development. I started with Visual Studio (VS).

Yet, no one seems to mentioned the __interface in VS (.NET). I am not very sure if this is a good way to declare an interface. But it seems to provide an additional enforcement (mentioned in the documents). Such that you don't have to explicitly specify the virtual TYPE Method() = 0;, since it will be automatically converted.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

However, I don't use it because I am concern about the cross platform compilation compatibility, since it only available under .NET.

If anyone do have anything interesting about it, please share. :-)

Thanks.


While it's true that virtual is the de-facto standard to define an interface, let's not forget about the classic C-like pattern, which comes with a constructor in C++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

This has the advantage that you can re-bind events runtime without having to construct your class again (as C++ does not have a syntax for changing polymorphic types, this is a workaround for chameleon classes).

Tips:

  • You might inherit from this as a base class (both virtual and non-virtual are permitted) and fill click in your descendant's constructor.
  • You might have the function pointer as a protected member and have a public reference and/or getter.
  • As mentioned above, this allows you to switch the implementation in runtime. Thus it's a way to manage state as well. Depending on the number of ifs vs. state changes in your code, this might be faster than switch()es or ifs (turnaround is expected around 3-4 ifs, but always measure first.
  • If you choose std::function<> over function pointers, you might be able to manage all your object data within IBase. From this point, you can have value schematics for IBase (e.g., std::vector<IBase> will work). Note that this might be slower depending on your compiler and STL code; also that current implementations of std::function<> tend to have an overhead when compared to function pointers or even virtual functions (this might change in the future).

Here is the definition of abstract class in c++ standard

n4687

13.4.2

An abstract class is a class that can be used only as a base class of some other class; no objects of an abstract class can be created except as subobjects of a class derived from it. A class is abstract if it has at least one pure virtual function.


class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

Result: Rectangle area: 35 Triangle area: 17

We have seen how an abstract class defined an interface in terms of getArea() and two other classes implemented same function but with different algorithm to calculate the area specific to the shape.

참고URL : https://stackoverflow.com/questions/318064/how-do-you-declare-an-interface-in-c

반응형