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()
가 호출되지 않는 것을 볼 수 있습니다.
내 대답은 기본적으로 다른 것과 동일하지만 다른 두 가지 중요한 일이 있다고 생각합니다.
인터페이스에서 가상 소멸자를 선언하거나 누군가 유형의 개체를 삭제하려고 할 때 정의되지 않은 동작을 방지하기 위해 보호 된 비가 상 소멸자를 만듭니다
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;
// ...
};
이 경우 인터페이스에는 참조 의미가 있습니다. 즉, 객체가 인터페이스보다 오래 지속되는지 확인해야합니다 (값 의미를 가진 인터페이스를 만들 수도 있음).
이러한 유형의 인터페이스에는 장단점이 있습니다.
- 그들은 더 많은 메모리를 필요로 상속 기반 다형성보다 더합니다.
- 그들은 빠른 일반적입니다 상속 기반 다형성보다.
- 최종 유형을 알고있는 경우 훨씬 더 빠릅니다! (gcc 및 clang과 같은 일부 컴파일러는 가상 함수가있는 유형에서 상속되지 않는 유형에서 더 많은 최적화를 수행합니다.)
마지막으로, 상속은 복잡한 소프트웨어 설계에서 모든 악의 근원입니다. 에서 숀 부모의 값 의미와 다형성을 개념 기반 (추천,이 기술의 더 나은 버전이 설명되어 있습니다) 다음과 같은 경우가 연구되고있다 :
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
어떻게 될까요?).
업데이트 : 비상 속 기반 다형성에 대한 몇 가지 새로운 참조가 있습니다.
- Sean Parent 's Inheritance는 사악한 대화 의 기본 클래스입니다 .
- Sean Parent의 가치 의미론과 개념 기반 다형성 이야기.
- Pyry Jahkola의 Inheritance free polymorphism talk and the poly library docs .
- Zach Laine의 실용적인 유형 삭제 : 우아한 디자인 패턴으로 OOP 문제 해결 이야기.
- Andrzej의 C ++ 블로그-Type Erasure 파트 i , ii , iii , iv .
- 런타임 다형성 제네릭 프로그래밍-ConceptC ++에서 객체와 개념 혼합
- Boost.TypeErasure 문서
- Adobe Poly 문서
- Boost.Any , std :: any 제안 (개정판 3) , Boost.Spirit :: hold_any .
위의 모든 좋은 답변. 명심해야 할 한 가지 추가 사항은 순수 가상 소멸자를 가질 수도 있다는 것입니다. 유일한 차이점은 여전히이를 구현해야한다는 것입니다.
혼란스러워?
--- 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 apublic
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
if
s vs. state changes in your code, this might be faster thanswitch()
es orif
s (turnaround is expected around 3-4if
s, but always measure first. - If you choose
std::function<>
over function pointers, you might be able to manage all your object data withinIBase
. From this point, you can have value schematics forIBase
(e.g.,std::vector<IBase>
will work). Note that this might be slower depending on your compiler and STL code; also that current implementations ofstd::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
'program story' 카테고리의 다른 글
이미 원격 브랜치에 푸시 된 병합 커밋을 되 돌리는 방법은 무엇입니까? (0) | 2020.09.29 |
---|---|
.gitignore 및 "체크 아웃시 다음 추적되지 않는 작업 트리 파일을 덮어 씁니다." (0) | 2020.09.29 |
.NET에서 어셈블리 바인딩 실패 로깅 (Fusion)을 활성화하는 방법 (0) | 2020.09.29 |
HTML 체크 박스를 읽기 전용으로 설정할 수 있습니까? (0) | 2020.09.29 |
단위, 기능, 승인 및 통합 테스트의 차이점은 무엇입니까? (0) | 2020.09.29 |