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


답변:


686

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
        }
};

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


106
가상 파괴자 ++! 이건 매우 중요합니다. 컴파일러가 자동으로 생성하는 것을 방지하기 위해 operator =의 순수 가상 선언을 포함하고 생성자 정의를 복사 할 수도 있습니다.
xan

33
가상 소멸자에 대한 대안은 보호 소멸자입니다. 이것은 다형성 파괴를 불가능하게하며, 이는 일부 상황에서 더 적합 할 수 있습니다. gotw.ca/publications/mill18.htm 에서 "Guideline # 4"를 찾으십시오 .
Fred Larson

9
다른 옵션은 =0본문 을 사용하여 순수한 가상 ( ) 소멸자 를 정의 하는 것입니다. 여기서 장점은 컴파일러가 이론적으로 vtable에 유효한 멤버가 없다는 것을 알 수 있으며 완전히 버릴 수 있다는 것입니다. 바디가있는 가상 소멸자에서, 소멸자는 (가상적으로) this포인터 를 통해 구성 도중에 (가상적으로) 호출 될 수 있으며 ( Parent따라서 생성 된 객체가 여전히 유형일 때) 컴파일러는 유효한 vtable을 제공해야합니다. 따라서 this생성하는 동안 가상 소멸자를 명시 적으로 호출하지 않으면 :) 코드 크기를 절약 할 수 있습니다.
Pavel Minaev

51
최상위 답변이 질문에 직접 대답하지 않는 (일반적으로 코드는 완벽하지만) C ++ 답변의 전형적인 방법 대신 간단한 답변을 최적화합니다.
Tim

18
C ++ 11 override에서는 컴파일 타임 인수 및 반환 값 유형 검사를 위해 키워드를 지정할 수 있습니다 . 예를 들어, Child의 선언에서virtual void OverrideMe() override;
Sean

245

순수한 가상 메소드로 클래스를 만드십시오. 해당 가상 메소드를 대체하는 다른 클래스를 작성하여 인터페이스를 사용하십시오.

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

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

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

29
IDemo에는 소멸자가 없어야 할 행동으로 정의됩니다. / * 무엇이든 * / delete p;
에반 테란

11
Child 클래스의 OverrideMe 메소드가 가상 인 이유는 무엇입니까? 그게 필요한가요?
Cemre

9
@Cemre-필요하지 않지만 아프지 않습니다.
PowerApp101

11
일반적으로 가상 메소드를 대체 할 때마다 키워드 '가상'을 유지하는 것이 좋습니다. 필수는 아니지만 코드를 더 명확하게 만들 수 있습니다. 그렇지 않으면 해당 메소드를 다형성으로 사용할 수 있거나 기본 클래스에 존재할 수 있다는 표시가 없습니다.
Kevin

27
@Kevin overrideC ++ 11에서 제외
keyser

146

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

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


17
인터페이스를 생성하여 입력하는 것을 막아주는 것이 여전히 좋을 것입니다 (가상, = 0, 가상 소멸자). 또한 다중 상속은 나에게 정말 나쁜 생각처럼 보이고 실제로 사용되는 것을 본 적이 없지만 인터페이스는 항상 필요합니다. 나쁜 점은 C ++ 커뮤니티가 내가 원하기 때문에 인터페이스를 소개하지는 않습니다.
Ha11owed

9
Ha11owed : 인터페이스가 있습니다. 그것들은 순수한 가상 메소드와 메소드 구현이없는 클래스라고합니다.
Miles Rout

6
@doc : java.lang.Thread에는 객체에 원하지 않는 메소드와 상수가 있습니다. Thread와 공개 메소드 checkAccess ()를 사용하는 다른 클래스에서 확장하면 컴파일러는 어떻게해야합니까? C ++에서와 같이 강력한 이름의 기본 포인터를 사용 하시겠습니까? 이것은 나쁜 디자인처럼 보이지만 일반적으로 다중 상속이 필요하다고 생각하는 구성이 필요합니다.
Ha11owed

4
@ Ha11 오래 전에 세부 사항을 기억하지 못했지만 클래스에 갖고 싶었던 메소드와 상수가 있었고 더 중요하게는 파생 클래스 객체가 Thread인스턴스가 되기를 원했습니다 . 다중 상속은 디자인과 구성이 잘못 될 수 있습니다. 그것은 모두 사건에 달려 있습니다.
doc

2
@ 데이브 : 정말요? Objective-C에는 컴파일 타임 평가 및 템플릿이 있습니까?
중복 제거기

51

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

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

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

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

추상 기본 클래스는 순수한 가상이 아닌 데이터 멤버 및 멤버 함수를 포함 할 수 있기 때문에 인터페이스 이상일 수 있습니다. 인터페이스에 해당하는 것은 순수한 가상 함수 만있는 데이터가없는 추상 기본 클래스입니다.

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


13
다중 상속을 대체하기 위해 "다중 상속 부족"이상을 말합니다. Java는 다중 상속이 해결하는 것보다 많은 문제를 발생시키기 때문에 처음부터 이와 같이 설계되었습니다. 좋은 답변
OscarRyz

11
오스카, 그것은 당신이 Java를 배운 C ++ 프로그래머인지 그 반대인지에 달려 있습니다. :) IMHO는 C ++의 거의 모든 것과 마찬가지로 신중하게 사용하면 여러 상속이 문제를 해결합니다. "인터페이스"추상 기본 클래스는 다중 상속을 매우 신중하게 사용하는 예입니다.
Dima

8
@OscarRyz 잘못되었습니다. MI는 잘못 사용될 때만 문제를 일으 킵니다. MI와 관련된 대부분의 주장 된 문제는 대체 설계 (MI없이)를 야기 할 것입니다. 사람들이 MI 디자인에 문제가있을 때, 그것은 MI의 잘못입니다. SI에 대한 설계 문제가있는 경우 자체 결함입니다. "죽음의 다이아몬드"(반복 된 상속)가 대표적인 예입니다. MI bashing은 순수한 위선이 아니라 가깝습니다.
curiousguy

4
의미 상 인터페이스는 추상 클래스와 다르므로 Java 인터페이스는 기술적 인 해결책이 아닙니다. 인터페이스 또는 추상 클래스 정의 사이의 선택은 기술적 고려 사항이 아니라 시맨틱에 의해 결정됩니다. 인터페이스 "HasEngine"을 상상해 봅시다 : 그것은 양상, 특징이며, 매우 다른 유형 (클래스 또는 추상 클래스)에 적용 / 구현 될 수 있으므로 추상 클래스가 아닌 인터페이스를 정의 할 것입니다.
Marek Stanley

2
@MarekStanley, 당신이 옳을 지 모르지만 더 좋은 예를 선택하기를 바랍니다. 인터페이스 상속과 구현 상속 측면에서 생각하고 싶습니다. C ++에서는 인터페이스와 구현을 함께 상속하거나 (공개 상속) 구현 만 상속 할 수 있습니다 (개인 상속). Java에서는 구현하지 않고 인터페이스 만 상속 할 수 있습니다.
Dima

43

테스트 할 수있는 한 가상 소멸자를 추가하는 것이 매우 중요합니다. 로 만들고 만든 개체를 사용 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()가 호출되지 않는 것을 알 수 있습니다.


3
실용적이고 컴파일 가능한 예제를 제공하여이 페이지를 가장 잘 설명합니다. 건배!
Lumi

1
Testet :: ~ Tester ()는 obj가 "Tester로 선언 된"경우에만 실행됩니다.
Alessandro L.

실제로, 문자열 privatename의 소멸자가 호출되고 메모리에서 할당되는 모든 것입니다. 런타임에 관한 한, 클래스의 모든 구체적인 멤버가 파괴되면 클래스 인스턴스도 파괴됩니다. 두 개의 Point 구조체가있는 Line 클래스로 비슷한 실험을 시도했으며 삭제 호출 또는 포함 함수에서 반환 할 때 두 구조체가 모두 파괴 (Ha!)되었음을 알았습니다. valgrind는 0 누출을 확인했습니다.
Chris Reid

33

내 대답은 기본적으로 다른 답변과 동일하지만 수행해야 할 다른 중요한 두 가지가 있다고 생각합니다.

  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
            }
    }

2
인터페이스에 데이터 멤버가 없으므로 가상 상속이 필요하지 않습니다.
Robocide

3
가상 상속은 메소드에도 중요합니다. 그것없이, 당신은 그것의 '인스턴스'중 하나가 순수한 가상이라도 (OverrideMe ())로 모호성을 겪게 될 것입니다.
Knarf Navillus 0시 22 분

5
@Avishay_ " 인터페이스에 데이터 멤버가 없으므로 가상 상속이 필요하지 않습니다. "잘못되었습니다.
curiousguy

WinAVR 2010과 함께 제공되는 버전 4.3.3과 같은 일부 gcc 버전에서는 가상 상속이 작동하지 않을 수 있습니다. gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu

비가 상 보호 소멸자를 가지고 -1 죄송합니다
Wolf

10

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

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


5
TBH 상속은 인터페이스 인 척하는 C ++ 11보다 훨씬 더 명확하지만 일관성이없는 디자인을 묶는 접착제입니다. 도형 예제는 현실에서 분리되고 Circle클래스는 열악한 디자인입니다. Adapter이런 경우 패턴을 사용해야합니다 . 조금 거칠게 들리면 미안하지만 Qt상속에 대한 판단을 내리기 전에 실제 라이브러리를 사용해보십시오 . 상속은 인생을 훨씬 쉽게 만듭니다.
doc

2
전혀 거칠게 들리지 않습니다. 도형 예제는 현실과 어떻게 분리되어 있습니까? Adapter패턴을 사용하여 Circle을 고정하는 예 (아이디어에있을 수 있음)를 제공 할 수 있습니까? 그 장점을보고 싶습니다.
gnzlbg

이 작은 상자에 들어 가려고 노력하겠습니다. 우선, 작업을 안전하게하기 위해 자신의 응용 프로그램을 작성하기 전에 "MyShape"와 같은 라이브러리를 선택하십시오. 그렇지 않으면 어떻게 Square거기에 아직 없는지 알 수 있습니까? 예지? 그것이 현실과 분리 된 이유입니다. 실제로 "MyShape"라이브러리를 사용하기로 선택한 경우 처음부터 인터페이스를 채택 할 수 있습니다. 모양 예제에는 많은 넌센스가 있지만 (그중 하나는 두 개의 Circle구조체 가 있다는 것입니다 ) 어댑터는 다음과 같이 보일 것입니다-> ideone.com/UogjWk
doc

2
그것은 현실에서 분리되지 않습니다. 회사 A가 회사 B를 구매하고 회사 B의 코드베이스를 A에 통합하려는 경우 완전히 독립적 인 두 개의 코드베이스가 있습니다. 각각 다른 유형의 모양 계층 구조가 있다고 가정하십시오. 상속과 쉽게 결합 할 수 없으며 회사 C를 추가하면 큰 혼란이 있습니다. 이 강연을 시청해야한다고 생각합니다. youtube.com/watch?v=0I0FD3N5cgM 내 대답은 더 오래되었지만 유사점을 보게 될 것입니다. 항상 모든 것을 다시 구현할 필요는 없으며 인터페이스에서 구현을 제공하고 사용 가능한 경우 멤버 함수를 선택할 수 있습니다.
gnzlbg

1
비디오의 일부를 보았는데 이것은 완전히 잘못되었습니다. 디버깅 목적을 제외하고는 dynamic_cast를 사용하지 않습니다. 동적 캐스트는 디자인에 문제가 있음을 의미하며이 비디오의 디자인은 의도적으로 잘못되었습니다. :). Guy는 심지어 Qt를 언급하지만 여기에서도 그는 틀 렸습니다-QLayout은 QWidget이나 다른 방법으로 상속받지 않습니다!
doc

9

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

혼란 스러운가?


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

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

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

내가하고 싶은 주된 이유는 내가 가지고있는 것처럼 인터페이스 메소드를 제공하고 싶지만 재정의를 선택 사항으로 만드는 것입니다.

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

파생 클래스에서 소멸자를 다시 구현하는 것은 전혀 중요하지 않습니다. 파생 클래스에서 항상 소멸자를 가상으로 구현하거나 다시 구현하지 않습니다.


4
왜 이런 이유에서 누군가가이 경우에 dtor를 순수한 가상으로 만들고 싶습니까? 그것의 이득은 무엇입니까? 파생 클래스에 포함 할 필요가없는 무언가 (dtor)를 강요하면됩니다.
Johann Gerell

6
귀하의 질문에 답변하기 위해 내 답변을 업데이트했습니다. 순수 가상 소멸자는 모든 메소드에 기본 구현이있는 인터페이스 클래스를 달성하는 유일한 방법 (유일한 방법은?)입니다.
Rodyland

7

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

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

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

인터페이스 코드가 훨씬 작고 생성 된 코드 크기가 훨씬 작을 수 있기 때문에이 방법이 마음에 듭니다. novtable을 사용하면 해당 클래스의 vtable 포인터에 대한 모든 참조가 제거되므로 직접 인스턴스화 할 수 없습니다. 여기에서 novtable 설명서를 참조하십시오 .


4
나는 왜 novtable표준을 넘어서 사용했는지 잘 모르겠습니다virtual void Bar() = 0;
Flexo

2
또한 추가했습니다 (방금 = 0;추가 한 것이 누락 된 것을 보았습니다). 이해가되지 않으면 설명서를 읽으십시오.
Mark Ingram

나는 그것없이 그것을 읽었고 그것이 = 0;정확히 똑같은 일을하는 비표준 방법이라고 생각했습니다.
Flexo

4

거기에 쓰여진 것에 약간의 추가 :

먼저 소멸자가 순수한 가상인지 확인하십시오.

둘째, 구현할 때 실질적인 조치를 취하기 위해 사실상 (일반적으로가 아니라) 상속을 원할 수 있습니다.


가상 상속은 개념적으로 상속 된 클래스의 인스턴스가 하나만 있다는 의미이기 때문에 가상 상속을 좋아합니다. 당연히 여기 클래스에는 공간이 필요하지 않으므로 불필요한 클래스 일 수 있습니다. C ++에서 MI를 한동안 수행하지 않았지만 비가 상 상속이 업 캐스팅을 복잡하게하지 않습니까?
Uri

왜 이런 이유에서 누군가가이 경우에 dtor를 순수한 가상으로 만들고 싶습니까? 그것의 이득은 무엇입니까? 파생 클래스에 포함 할 필요가없는 무언가 (dtor)를 강요하면됩니다.
Johann Gerell

2
인터페이스에 대한 포인터를 통해 객체가 파괴되는 상황이 발생하면 소멸자가 가상인지 확인해야합니다.
Uri

순수한 가상 소멸자에게는 아무런 문제가 없습니다. 꼭 필요한 것은 아니지만 아무 문제가 없습니다. 파생 클래스에서 소멸자를 구현하는 것은 해당 클래스의 구현 자에게 큰 부담이되지 않습니다. 왜 이렇게했는지 아래 답변을 참조하십시오.
Rodyland

인터페이스를 사용하면 클래스가 둘 이상의 경로에서 인터페이스를 파생시킬 가능성이 높기 때문에 가상 상속의 경우 +1입니다. 나는 인터페이스 tho에서 보호 된 소멸자를 선택합니다.
doc

4

NVI (Non Virtual Interface Pattern)로 구현 된 계약 클래스를 고려할 수도 있습니다. 예를 들어 :

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.
};

다른 독자들을 위해 Jim Hyslop과 Herb Sutter 의이 Dobbs 기사 "Conversations : Virtually Yours"는 왜 NVI를 사용하고 싶은지에 대해 좀 더 자세히 설명합니다.
user2067021

또한 Herb Sutter 의이 기사 "Virtuality".
user2067021

1

나는 여전히 C ++ 개발에 익숙하지 않습니다. Visual Studio (VS)로 시작했습니다.

그러나 아무도 __interfaceVS (.NET) 에 대해 언급하지 않은 것 같습니다 . 나는 하지 매우 확실이 인터페이스를 선언 할 수있는 좋은 방법 인 경우. 그러나 추가 시행 을 제공하는 것 같습니다 ( 문서 참조 ). virtual TYPE Method() = 0;자동으로 변환 되므로을 명시 적으로 지정할 필요가 없습니다 .

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

그러나 크로스 플랫폼 컴파일 호환성은 .NET에서만 사용할 수 있기 때문에 크로스 플랫폼 컴파일 호환성에 대해 걱정하기 때문에 사용하지 않습니다.

누군가 흥미로운 점이 있으면 공유하십시오. :-)

감사.


0

virtual인터페이스를 정의하는 사실상의 표준은 사실이지만 C ++의 생성자와 함께 제공되는 고전적인 C 유사 패턴을 잊지 마십시오.

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

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

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

이것은 클래스를 다시 구성하지 않고도 이벤트 런타임을 리 바인드 할 수 있다는 이점이 있습니다 (C ++에는 다형성 유형을 변경하기위한 구문이 없으므로 카멜레온 클래스에 대한 해결 방법입니다).

팁 :

  • 이것을 기본 클래스로 상속하고 (가상 및 비가 상 모두 허용됨) click자손의 생성자를 채울 수 있습니다.
  • 함수 포인터를 protected멤버로 사용하고 public참조 및 / 또는 getter를 가질 수 있습니다 .
  • 위에서 언급 한 것처럼 런타임에서 구현을 전환 할 수 있습니다. 따라서 상태를 관리하는 방법이기도합니다. if코드 의 s 대 상태 변경 횟수에 따라 es 또는 s 보다 빠를 있습니다 (약 3-4 초 정도 소요 되지만 항상 먼저 측정하십시오).switch()ifif
  • std::function<>함수 포인터를 초과 선택 하면의 모든 객체 데이터를 관리 할 있습니다 IBase. 이 시점에서 가치 회로도를 가질 수 있습니다 IBase(예 : std::vector<IBase>작동). 참고이 있다고 할 수 컴파일러 및 STL 코드에 따라 속도가 느려질 수; 또한 현재 구현은 std::function<>함수 포인터 또는 가상 함수와 비교할 때 오버 헤드가있는 경향이 있습니다 (향후 변경 될 수 있음).

0

다음은 abstract classC ++ 표준 의 정의입니다 .

n4687

13.4.2

추상 클래스는 다른 클래스의 기본 클래스로만 사용할 수있는 클래스입니다. 파생 클래스의 하위 객체를 제외하고 추상 클래스의 객체를 만들 수 없습니다. 하나 이상의 순수 가상 함수가있는 클래스는 추상적입니다.


-2
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;
}

결과 : 사각형 영역 : 35 삼각형 영역 : 17

우리는 추상 클래스가 getArea ()의 관점에서 인터페이스를 정의한 방법과 다른 두 개의 클래스가 동일한 기능을 구현하지만 모양에 특정한 영역을 계산하는 다른 알고리즘을 사용하는 방법을 보았습니다.


5
이것은 인터페이스로 간주되는 것이 아닙니다! 그것은 재정의해야 할 하나의 메소드가있는 추상 기본 클래스입니다! 인터페이스는 일반적으로 메소드 정의 만 포함하는 객체입니다. 다른 클래스는 인터페이스를 구현할 때 수행해야하는 "계약"입니다.
guitarflow
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.