2009-08-01 4 views
31

C++에는 Java 및 C#의 interface 기능이 없으므로 C++ 클래스에서 인터페이스를 시뮬레이트하는 데 가장 적합한 방법은 무엇입니까? 내 추측은 추상 클래스의 다중 상속 것입니다.메모리 오버 헤드/성능에 어떤 영향이 있습니까? SerializableInterface과 같은 시뮬레이트 된 인터페이스에 대한 명명 규칙이 있습니까?C++에서 인터페이스를 시뮬레이트하려면 어떻게해야합니까?

+9

인터페이스가 부족합니다. 그것은 부정적인 표현처럼 보입니다. C++은 인터페이스가 부족하지 않습니다 (클래스는 인터페이스입니다). 키워드 인터페이스가 부족합니다. 왜곡되지 않았기 때문입니다. –

+3

Interface 키워드는 코드 나 데이터가 포함되지 않도록 보장되어 다른 인터페이스와 쉽게 상호 작용할 수 있습니다. C++에서 이러한 보증을 할 수있는 방법이 없습니다. 충돌이 발생하지 않기를 바랄뿐입니다. Java와 C#이 코드 가독성, 상호 운용성 및 이해 가능성을 위해 많은 것들이 C++로 작업하는 동안 사람들이 겪었던 문제에 대한 응답으로 파악되었습니다. –

+2

[C++에서 인터페이스를 어떻게 선언합니까?] (http://stackoverflow.com/questions/318064/how-do-you-declare-an-interface-in-c) –

답변

29

C++에는 C# 및 Java와 달리 다중 상속이 있으므로 일련의 추상 클래스를 만들 수 있습니다.

관습에 관해서는 당신에게 달려 있습니다. 그러나, 나는 I.

class IStringNotifier 
{ 
public: 
    virtual void sendMessage(std::string &strMessage) = 0; 
    virtual ~IStringNotifier() { } 
}; 

성능은 C# 및 자바의 비교의 관점에서 걱정할 필요 없다와 클래스 이름 앞에 것을 좋아합니다. 기본적으로 함수에 대한 조회 테이블이나 가상 메소드가있는 상속과 같은 vtable이있는 오버 헤드가 있습니다.

+5

추상 클래스 만들기 추상 클래스에 대한 포인터를 사용하여 객체를 삭제하려는 경우 가상 소멸자. –

+0

@ 황홍 : 동의가 명시 적으로 추가되었습니다. –

+9

@Michael Aaron Safyan : downvote에 감사드립니다. 코딩 스타일에 대한 선호가 아닌 답을 통해 질문을 판단하면 좋을지도 모릅니다. –

3

C++의 인터페이스는 순수 가상 함수 만 갖는 클래스입니다. 예 : :

class ISerializable 
{ 
public: 
    virtual ~ISerializable() = 0; 
    virtual void serialize(stream& target) = 0; 
}; 

이것은 시뮬레이션 된 인터페이스가 아니며 Java의 인터페이스와 유사하지만 단점을 가지고 있지 않습니다.

예. 당신은 부정적인 결과없이 방법과 구성원을 추가 할 수 있습니다 :

이름 지정 규칙에
class ISerializable 
{ 
public: 
    virtual ~ISerializable() = 0; 
    virtual void serialize(stream& target) = 0; 
protected: 
    void serialize_atomic(int i, stream& t); 
    bool serialized; 
}; 

...는 C++ 언어로 정의 된 실제 명명 규칙이 없습니다. 그래서 당신의 환경에서 하나를 선택하십시오.

오버 헤드는 1 개의 정적 테이블이며 아직 가상 함수가없는 파생 클래스에서 정적 테이블에 대한 포인터입니다.

+1

나는 가상 생성자를 가질 수 있다고 생각하지 않는다. 가상 소멸자를 가질 수 있습니다. – jkeys

+0

@hooked, 타이핑 오류가 수정되었습니다. – Christopher

+1

소멸자가 순수 가상이되는 이유는 없습니다. 그냥 평범한 가상으로 충분할 것입니다. 또한 dtor을 선언하는 것은 이 아니므로 정의해야합니다. 순수 가상 dtor 의 경우에는 다음과 같이 클래스 정의 외부에서 정의해야합니다. ISerializable :: ~ ISerializable() {} C++ 문법이 순수 가상 지정자 과 클래스 멤버 함수를 모두 허용하지 않기 때문에 정의. –

1

가상 상속을 사용하지 않는 경우 오버 헤드는 최소한 하나 이상의 가상 함수가있는 일반적인 상속보다 나쁘지 않아야합니다. 상속받은 각 추상 클래스는 각 객체에 대한 포인터를 추가합니다. 당신은 빈 기본 클래스 최적화 뭔가를 할 경우

그러나, 당신은을 최소화 할 수 있습니다 :

 
struct A 
{ 
    void func1() = 0; 
}; 

struct B: A 
{ 
    void func2() = 0; 
}; 

struct C: B 
{ 
    int i; 
}; 

C의 크기를 두 단어 일 것이다.

7

"메모리 오버 헤드/성능에 어떤 영향이 있습니까?"

일반적으로 가상 전화를 사용하는 경우를 제외하고는 성능 측면에서 표준에 의해 보장되는 것은 아무것도 없습니다.

메모리 오버 헤드에서 "빈 기본 클래스"최적화는 컴파일러가 데이터 멤버가없는 기본 클래스를 추가해도 개체 크기가 증가하지 않도록 레이아웃 구조를 명시 적으로 허용합니다. 나는 이것을하지 않는 컴파일러를 다루지는 않을 것 같지만 잘못 될 수 있습니다.

클래스에 첫 번째 가상 멤버 함수를 추가하면 일반적으로 가상 멤버 함수가없는 경우와 비교하여 포인터 크기만큼 개체가 증가합니다. 가상 멤버 함수를 추가하면 추가적인 차이가 없습니다. 가상 기본 클래스를 추가하면 더 많은 차이를 만들 수 있지만, 사용자가 말하는 것에 대해서는 필요하지 않습니다.

가상 멤버 함수를 사용하여 여러 기본 클래스를 추가하는 것은 실제로 일반적인 구현에서 개체에 여러 vtable 포인터가 필요하기 때문에 실제로는 기본 클래스 최적화를 한 번만 수행한다는 것을 의미합니다. 따라서 각 클래스에 여러 인터페이스가 필요한 경우 개체 크기에 추가 할 수 있습니다.

성능면에서 가상 함수 호출은 비 가상 함수 호출보다 약간 더 많은 오버 헤드가 있으며 더 중요한 것은 일반적으로 (항상?) 인라인되지 않는다고 가정 할 수 있습니다. 빈 기본 클래스를 추가하는 것은 일반적으로 빈 기본 생성자 및 소멸자가 파생 클래스 생성자/소멸자 코드로 인라인 될 수 있으므로 생성 또는 삭제에 코드를 추가하지 않습니다.

명시 적 인터페이스를 원한다면 가상 기능을 피하기 위해 사용할 수있는 트릭이 있지만 동적 다형성은 필요하지 않습니다. 그러나 자바를 에뮬레이션하려는 경우 그럴 수 없다고 가정합니다.

예제 코드 :

#include <iostream> 

// A is an interface 
struct A { 
    virtual ~A() {}; 
    virtual int a(int) = 0; 
}; 

// B is an interface 
struct B { 
    virtual ~B() {}; 
    virtual int b(int) = 0; 
}; 

// C has no interfaces, but does have a virtual member function 
struct C { 
    ~C() {} 
    int c; 
    virtual int getc(int) { return c; } 
}; 

// D has one interface 
struct D : public A { 
    ~D() {} 
    int d; 
    int a(int) { return d; } 
}; 

// E has two interfaces 
struct E : public A, public B{ 
    ~E() {} 
    int e; 
    int a(int) { return e; } 
    int b(int) { return e; } 
}; 

int main() { 
    E e; D d; C c; 
    std::cout << "A : " << sizeof(A) << "\n"; 
    std::cout << "B : " << sizeof(B) << "\n"; 
    std::cout << "C : " << sizeof(C) << "\n"; 
    std::cout << "D : " << sizeof(D) << "\n"; 
    std::cout << "E : " << sizeof(E) << "\n"; 
} 

출력 (32 비트 플랫폼에서 GCC는) : 아무것도없는

있다
A : 4 
B : 4 
C : 8 
D : 8 
E : 12 
5

정말 그 C++ 아니므로 무엇을 '시뮬레이션'을 할 필요가 없다는 것을 자바 인터페이스로 할 수 있습니다.

Java는 뷰의 C++ 포인터에서 interfaceclass 사이에 "인공적인"구별을합니다. interface은 모두 단지 class이며 모든 메소드가 추상적이고 데이터 멤버를 포함 할 수 없습니다.

Java는 제한되지 않은 다중 상속을 허용하지 않기 때문에이 제한을 적용하지만 class에서 implement 개의 여러 인터페이스를 허용합니다.

C++에서, classclass이고 interfaceclass입니다. extends은 공개 상속으로 이루어지며 implements은 공개 상속으로도 달성됩니다.

여러 인터페이스가 아닌 클래스에서 상속하면 추가적인 문제가 발생할 수 있지만 상황에 따라 유용 할 수 있습니다. 하나의 비 인터페이스 클래스와 완전히 추상적 인 클래스에서 상속하는 클래스로만 자신을 제한하면 Java (다른 C++/Java 차이점 제외)보다 다른 어려움이 발생하지 않습니다.

메모리 및 오버 헤드 비용 측면에서 Java 스타일 클래스 계층 구조를 다시 만드는 경우 어쨌든 클래스에 가상 함수 비용을 지불했을 것입니다. 어쨌든 서로 다른 런타임 환경을 사용한다고 가정하면 서로 다른 상속 모델의 비용면에서 두 가지 사이의 오버 헤드에 근본적인 차이는 없을 것입니다.

1

MSVC 2008의 키워드는 __interface입니다.

A Visual C++ interface can be defined as follows: 

- Can inherit from zero or more base 
    interfaces. 
- Cannot inherit from a base class. 
- Can only contain public, pure virtual 
    methods. 
- Cannot contain constructors, 
    destructors, or operators. 
- Cannot contain static methods. 
- Cannot contain data members; 
    properties are allowed. 

이 기능은 Microsoft 특정 기능입니다. 주의 : __interface에는 인터페이스 포인터로 개체를 삭제할 때 필요한 가상 소멸자가 없습니다.

+0

이것이 C++을 수행하는 COM/DCOM의 방식이라고 생각합니다. –

+0

왜 그렇게 생각하니? –

+0

COM 인터페이스로 작업 할 때 사용하는 유일한 장소이기 때문에 ;-) 사용했습니다. –

1

C++에서 우리는 평범한 비헤이비어가 아닌 Java & 인터페이스보다 더 나아갈 수 있습니다. NVI 패턴을 사용하여 명시 적 계약 (계약 : 계약)을 추가 할 수 있습니다.

struct Contract1 : noncopyable 
{ 
    virtual ~Contract1(); 
    Res f(Param p) { 
     assert(f_precondition(p) && "C1::f precondition failed"); 
     const Res r = do_f(p); 
     assert(f_postcondition(p,r) && "C1::f postcondition failed"); 
     return r; 
    } 
private: 
    virtual Res do_f(Param p) = 0; 
}; 

struct Concrete : virtual Contract1, virtual Contract2 
{ 
    ... 
}; 
0

요청하는 방식대로 인터페이스를 구현하는 좋은 방법은 없습니다. 완전히 추상적 인 ISerializable 기본 클래스와 같은 접근법의 문제점은 C++이 다중 상속을 구현하는 방식에 있습니다. 다음 고려 :

class Base 
{ 
}; 
class ISerializable 
{ 
    public: 
    virtual string toSerial() = 0; 
    virtual void fromSerial(const string& s) = 0; 
}; 

class Subclass : public Base, public ISerializable 
{ 
}; 

void someFunc(fstream& out, const ISerializable& o) 
{ 
    out << o.toSerial(); 
} 

가 분명히 의도는 함수 toSerial()는 기본 클래스에서 상속을 포함한 서브 클래스의 모든 멤버를 직렬화하기위한 것입니다. 문제는 ISerializable에서 Base 로의 경로가 없다는 것입니다.

void fn(Base& b) 
{ 
    cout << (void*)&b << endl; 
} 
void fn(ISerializable& i) 
{ 
    cout << (void*)&i << endl; 
} 

void someFunc(Subclass& s) 
{ 
    fn(s); 
    fn(s); 
} 

첫 번째 호출에 의한 값의 출력은 두 번째 호출에 의한 값의 출력과 동일하지 않습니다 : 다음 실행할 경우 그래픽으로 볼 수 있습니다. 두 경우 모두에서 s에 대한 참조가 전달 되더라도 컴파일러는 전달 된 주소를 적절한 기본 클래스 유형과 일치하도록 조정합니다.

관련 문제