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 lingo의 메소드)가 다음 구문을 사용하여 선언 된 순수한 가상 함수 인 클래스입니다.

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. 그리기 기능을 호출하면 어떻게됩니까 ?).

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