2009-08-01 11 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#とJavaとの比較の観点から何も心配することはありません。基本的には、関数のルックアップテーブルを持つオーバーヘッドや、仮想メソッドを使用した継承のようなvtableを持つだけです。

+5

抽象クラスを作成する抽象クラスへのポインタを使用してオブジェクトを削除する場合は、仮想デストラクタを使用します。 –

+0

@黄山(Yim Huang):同意して明示的に追加されました。 –

+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

仮想継承を使用しない場合は、少なくとも1つの仮想関数を使用する通常の継承よりもオーバーヘッドが悪くならないようにする必要があります。継承された各抽象クラスは、各オブジェクトへのポインタを追加します。

あなたは空の基本クラスの最適化のような何かを行う場合は、あなたがそれを最小限に抑えることができますCの

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

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

struct C: B 
{ 
    int i; 
}; 

サイズは二つの単語になります。

7

「メモリのオーバーヘッド/パフォーマンスに関してどのような影響がありますか?」

通常、パフォーマンス上の基準では何も保証されていませんが、仮想呼び出しを使用した場合を除き、通常はありません。

メモリオーバーヘッドでは、「空の基本クラス」最適化により、データメンバーのない基本クラスを追加してもオブジェクトのサイズが増えることはありません。私はあなたがこれをしないコンパイラに対処する必要はないと思うが、私は間違っている可能性があります。

クラスに最初の仮想メンバ関数を追加すると、通常、仮想メンバ関数がない場合と比較して、ポインタのサイズだけオブジェクトが増加します。仮想メンバ関数を追加することで、追加の違いはありません。仮想基底クラスを追加することでさらなる違いが生まれるかもしれませんが、あなたが話していることについては必要ありません。

仮想メンバー関数で複数の基本クラスを追加すると、実際には、通常の実装では複数のvtableポインタが必要になるため、実際には空の基本クラスの最適化が1回だけ行われることを意味します。したがって、各クラスに複数のインターフェイスが必要な場合は、オブジェクトのサイズに追加することができます。

パフォーマンス上、仮想関数呼び出しは、非仮想関数呼び出しよりも少しオーバーヘッドがあります。さらに重要なことに、一般的に(常に?)インライン化されないと想定できます。空の基本コンストラクタとデストラクタは、派生クラスのコンストラクタ/デストラクタコードにインライン化できるため、空の基本クラスを追加することは、通常、構築や破壊にコードを追加しません。

明示的なインターフェイスが必要な場合は仮想機能を回避するためのトリックがありますが、動的なポリモーフィズムは必要ありません。しかし、あなたがJavaをエミュレートしようとしているなら、そうではないと私は想定しています。

例コード:

#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の何かを欠落していることではないとして何かを 'シミュレート' する必要は本当にありませんインターフェイスで行うことができます。

Javaは、ビューのC++ポインターから、interfaceclassの間に「人工的」な区別をします。 interfaceは、すべてが抽象的なものであり、データメンバーを含むことのできないメソッドがすべてclassです。

Javaでは制約のない多重継承はできませんが、classからimplementまでの複数のインタフェースが許可されているため、この制限が適用されます。

C++では、classclassであり、interfaceclassです。 extendsは公開継承によって達成され、implementsも公開継承によって達成される。

複数の非インターフェイスクラスから継承すると、余分な問題が発生することがありますが、状況によっては役に立ちます。たった一つの非インターフェイスクラスと完全抽象クラスからの継承クラスのみに制限すると、Java(他のC++/Javaの違いを除いて)と比べて他の困難に遭遇することはありません。

メモリとオーバーヘッドのコストの観点から、Javaスタイルのクラス階層を再作成する場合は、いずれにしてもクラスに仮想関数コストを支払った可能性があります。とにかく、さまざまなランタイム環境を使用しているとすれば、異なる継承モデルのコストの点で、2つの間のオーバーヘッドに根本的な違いはありません。

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の単純なビヘイビアレスインターフェイスよりもさらに進歩することができます& co。 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); 
} 

最初の呼び出しの出力値が2番目の呼び出しが出力する値と同じではありません。どちらの場合もsへの参照が渡されても、コンパイラは渡されたアドレスを適切な基本クラスの型に合わせて調整します。

関連する問題