답변:
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
본문 을 사용하여 순수한 가상 ( ) 소멸자 를 정의 하는 것입니다. 여기서 장점은 컴파일러가 이론적으로 vtable에 유효한 멤버가 없다는 것을 알 수 있으며 완전히 버릴 수 있다는 것입니다. 바디가있는 가상 소멸자에서, 소멸자는 (가상적으로) this
포인터 를 통해 구성 도중에 (가상적으로) 호출 될 수 있으며 ( Parent
따라서 생성 된 객체가 여전히 유형일 때) 컴파일러는 유효한 vtable을 제공해야합니다. 따라서 this
생성하는 동안 가상 소멸자를 명시 적으로 호출하지 않으면 :) 코드 크기를 절약 할 수 있습니다.
override
에서는 컴파일 타임 인수 및 반환 값 유형 검사를 위해 키워드를 지정할 수 있습니다 . 예를 들어, Child의 선언에서virtual void OverrideMe() override;
순수한 가상 메소드로 클래스를 만드십시오. 해당 가상 메소드를 대체하는 다른 클래스를 작성하여 인터페이스를 사용하십시오.
순수 가상 메소드는 가상으로 정의되고 0에 지정된 클래스 메소드입니다.
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
C ++ 11에서 제외
C # / Java의 추상 기본 클래스 외에 특별한 인터페이스 유형 범주 가있는 이유는 C # / Java가 다중 상속을 지원하지 않기 때문입니다.
C ++는 다중 상속을 지원하므로 특별한 유형이 필요하지 않습니다. 비 추상적 (순수 가상) 메소드가없는 추상 기본 클래스는 기능적으로 C # / Java 인터페이스와 동일합니다.
Thread
인스턴스가 되기를 원했습니다 . 다중 상속은 디자인과 구성이 잘못 될 수 있습니다. 그것은 모두 사건에 달려 있습니다.
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()
가 호출되지 않는 것을 알 수 있습니다.
내 대답은 기본적으로 다른 답변과 동일하지만 수행해야 할 다른 중요한 두 가지가 있다고 생각합니다.
인터페이스에서 가상 소멸자를 선언하거나 누군가가 유형의 객체를 삭제하려고 시도 할 때 정의되지 않은 동작을 피하기 위해 보호 된 비가 상 가상 소멸자를 만드십시오 IDemo
.
다중 상속 중에 문제점을 피하려면 가상 상속을 사용하십시오. (인터페이스를 사용할 때 여러 상속이 더 자주 발생합니다.)
그리고 다른 답변들처럼 :
해당 가상 메소드를 대체하는 다른 클래스를 작성하여 인터페이스를 사용하십시오.
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
. 그리기 기능을 호출하면 어떻게됩니까 ?).
비 상속 기반 다형성에 대한 몇 가지 새로운 참조가 있습니다.
Circle
클래스는 열악한 디자인입니다. Adapter
이런 경우 패턴을 사용해야합니다 . 조금 거칠게 들리면 미안하지만 Qt
상속에 대한 판단을 내리기 전에 실제 라이브러리를 사용해보십시오 . 상속은 인생을 훨씬 쉽게 만듭니다.
Adapter
패턴을 사용하여 Circle을 고정하는 예 (아이디어에있을 수 있음)를 제공 할 수 있습니까? 그 장점을보고 싶습니다.
Square
거기에 아직 없는지 알 수 있습니까? 예지? 그것이 현실과 분리 된 이유입니다. 실제로 "MyShape"라이브러리를 사용하기로 선택한 경우 처음부터 인터페이스를 채택 할 수 있습니다. 모양 예제에는 많은 넌센스가 있지만 (그중 하나는 두 개의 Circle
구조체 가 있다는 것입니다 ) 어댑터는 다음과 같이 보일 것입니다-> ideone.com/UogjWk
위의 모든 좋은 답변. 명심해야 할 한 가지 추가 사항-순수한 가상 소멸자를 가질 수도 있습니다. 유일한 차이점은 여전히 구현해야한다는 것입니다.
혼란 스러운가?
--- 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 */ }
}
인터페이스 코드가 훨씬 작고 생성 된 코드 크기가 훨씬 작을 수 있기 때문에이 방법이 마음에 듭니다. novtable을 사용하면 해당 클래스의 vtable 포인터에 대한 모든 참조가 제거되므로 직접 인스턴스화 할 수 없습니다. 여기에서 novtable 설명서를 참조하십시오 .
novtable
표준을 넘어서 사용했는지 잘 모르겠습니다virtual void Bar() = 0;
= 0;
추가 한 것이 누락 된 것을 보았습니다). 이해가되지 않으면 설명서를 읽으십시오.
= 0;
정확히 똑같은 일을하는 비표준 방법이라고 생각했습니다.
거기에 쓰여진 것에 약간의 추가 :
먼저 소멸자가 순수한 가상인지 확인하십시오.
둘째, 구현할 때 실질적인 조치를 취하기 위해 사실상 (일반적으로가 아니라) 상속을 원할 수 있습니다.
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.
};
나는 여전히 C ++ 개발에 익숙하지 않습니다. Visual Studio (VS)로 시작했습니다.
그러나 아무도 __interface
VS (.NET) 에 대해 언급하지 않은 것 같습니다 . 나는 하지 매우 확실이 인터페이스를 선언 할 수있는 좋은 방법 인 경우. 그러나 추가 시행 을 제공하는 것 같습니다 ( 문서 참조 ). virtual TYPE Method() = 0;
자동으로 변환 되므로을 명시 적으로 지정할 필요가 없습니다 .
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
그러나 크로스 플랫폼 컴파일 호환성은 .NET에서만 사용할 수 있기 때문에 크로스 플랫폼 컴파일 호환성에 대해 걱정하기 때문에 사용하지 않습니다.
누군가 흥미로운 점이 있으면 공유하십시오. :-)
감사.
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()
if
if
std::function<>
함수 포인터를 초과 선택 하면의 모든 객체 데이터를 관리 할 수 있습니다 IBase
. 이 시점에서 가치 회로도를 가질 수 있습니다 IBase
(예 : std::vector<IBase>
작동). 참고이 있다고 할 수 컴파일러 및 STL 코드에 따라 속도가 느려질 수; 또한 현재 구현은 std::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;
}
결과 : 사각형 영역 : 35 삼각형 영역 : 17
우리는 추상 클래스가 getArea ()의 관점에서 인터페이스를 정의한 방법과 다른 두 개의 클래스가 동일한 기능을 구현하지만 모양에 특정한 영역을 계산하는 다른 알고리즘을 사용하는 방법을 보았습니다.