2011-12-29 13 views
1

私はC++ STLコンテナベクトルのラッパーを構築しています(ベクトルが提供できる(ロード、保存など)ので、ラッパーにする必要があります) 。STLベクトル用のインターフェイスを構築する

私のラッパーのコンシューマは、要素を反復する必要があり、後で実装を変更するイテレータを公開すると、呼び出し元に再コンパイルする必要があります(さらに、カプセル化を破るように感じる)。

実装を変更した場合にクライアントが再コンパイルする必要がないように、プリミティブ型のみを返すインターフェイスを作成したいと考えています。私はベクトルサイズを整数(MFC CArrayのやり方に似ています)として公開し、[]演算子をオーバーロードして呼び出し元がそのようにベクトルをループすることを考えていました。

私の質問:

  1. 私はsize_typeでその作業を行う方法を、ベクトルの大きさのためにint型を返すようにしたい場合は? Size_typeはインタフェースに公開されるべきではないように感じます。なぜなら、呼び出し元が変更された場合、再コンパイルする必要があるからです。 size_typeが整数より大きい場合は、何らかの制限を課してもよろしいです(私はこれまで多くの要素を持っているとは思いません!)
  2. []演算子を使ってベクトルをループしていますこれは、テンプレートとは何の関係もありません、それだけでクラスだ -は、「一般的な」単語を削除しました:使用してイテレータ編集

、その後大幅に悪化。また、「プリミティブ型を公開する」とは、データメンバ自体ではなくintを返すメソッドを意味することも明らかにしました。

+2

イテレータと 'size_type'を使用することは、整数インデックスと' operator [] 'を使用するよりも一般的に_more_です。反復子はコンテナの型を抽象化し、 'size_type'はサイズの値を任意の整数型にします。 –

+0

後でBoostのようなものを使用するように実装を変更した場合、STLイテレータを使用していたクライアントは再コンパイルする必要がありますか? – TownCube

+1

ベクトルの場合と同じように、イテレータの独自のラッパーを記述することができます。しかし、はい、 'size_type'の型が変更され、それが公開されている場合、再コンパイルする必要があります。 –

答えて

2

クライアントが参照するシングルトンifc(純粋仮想)クラスを定義できます。私はこのデザインモデルを「シンゲルトン工場アプローチ」と呼んでいると思います。 私の長い答えがあなたを助けてくれることを願っています:)。

パブリックインターフェイス(メソッドリスト)を変更していない限り、コードを変更してもクライアントは再コンパイルする必要はありません。

何かのように:

myClassIfc.h:

Class myClassIfc 
{ 
public: 
    virtual ~myClassIfc();  

    ///// list all your pure virtual public ifc methods here //// 
    void m_zRunMyMethod(int nNumber) = 0; 
    int m_nSize() = 0; 

    static myClassIfc* ms_pGetImplObj();  

protected:   
    myClassIfc(); 
    static myClassIfc* ms_pImplObj; 
} 

inline myClassIfc* myClassIfc::ms_pGetImplObj() 
{ 
    return ms_pImplObj; 
} 

myClassIfc.cpp:

#include myClassIfc.h 

myClassIfc::myClassIfc() 
{ 
} 
myClassIfc::~myClassIfc() 
{ 
} 

myClass.h -

Class myClass: public myClassIfc 
{ 
public: 
    virtual ~myClass();  

    void m_zRunMyMethod(int nNumber); 
    int m_nSize(); 

    static void ms_zCreate(); 
    static void ms_zDestroy(); 

protected:   
    myClass(); 

private: 
    vector<int> myInternalVector; 
} 

あなたの純粋仮想クラスを実装するMyClassの.cpp:

#include myClass.h 

void myClass::m_zRunMyMethod(int nNumber) 
{ 
    /// your action 
    printf("%d\n", nNumber); 
} 

int myClass::m_nSize() 
{ 
    return int(myInternalVector.size()); 
} 

void myClass::ms_zCreate() 
{ 
    if (NULL != ms_pImplObj) 
    { 
     return; 
    } 
    ms_pImplObj = (myClass*) new myClass(); 
} 

void myClass::ms_zDestroy() 
{ 
    if (NULL == ms_pImplObj) 
    { 
     return; 
    } 
    delete ms_pImplObj; 
    ms_pImplObj = NULL; 
} 

は、今以上の長いインフラの仕事の後、あなたのクライアントは、私は上記なかった唯一のものは、シングルトンオブジェクト自体を作成し、誰を意味し、あなたのメモリ管理、(ある

#include myClassIfc.h 


void main(void) 
{ 
    myClassIfc::ms_pGetImplObj()->m_zRunMyMethod(5); 
    myClassIfc::ms_pGetImplObj()->m_nSize();   
} 

を使用する必要があります派生クラスの静的API ms_zCreate()を呼び出します)。 他の場所から呼び出すことも、コードから直接呼び出すこともできます。

上記のifcアプローチを非シェルトン実装にすることができます。 ifcクラスが変更されていない限り、派生(実装)クラスを変更した場合、クライアントコードは再コンパイルする必要はありません。

+0

は素晴らしい仕事のようです。 –

+0

継承を使用して実装の詳細を隠そうとするよりも、むしろpimplとforwardにするのがよいでしょう。 –

1

実際にインデックスと演算子[]を使用することはお勧めしません。ベクタイテレータを直接公開するのではなく、最初にベクタイテレータと同じイテレータtypedefをクラスに作成しますが、クライアントがコードを変更することなく必要なものに変更できます。その後、通常のbegin/end/findメソッドを使用することができます。

+0

私は、コードを変更する必要はないが、typedefが変更された場合に再コンパイルする必要があることを理解していますか? – TownCube

+0

はい。再コンパイルを避ける唯一の方法は、独自の対応するイテレータラッパーを作成し、ソースファイル内の実装全体を隠すことです。 ABIの破損(とにかく再コンパイルを強制する)は、C++では避けがたいものです。 –

1

現代的なC++デザインの面で、あなたが求めていることはたくさんあります。私はHerb StutterとAndrei Alexandrescuの本C++ Coding Standardsを紹介します。

Class Design and Inheritance 
32. Be clear what kind of class you’re writing. 56 
33. Prefer minimal classes to monolithic classes. 57 
34. Prefer composition to inheritance. 58 
35. Avoid inheriting from classes that were not designed to be base classes. 60 
38. Practice safe overriding. 66 
39. Consider making virtual functions nonpublic, and public functions nonvirtual. 68 
41. Make data members private, except in behaviorless aggregates (C-style structs). 72 
42. Don’t give away your internals. 74 
44. Prefer writing nonmember nonfriend functions. 79 

#44に特に注意してください。 STLベクトルをオーバーライドする代わりに、STLイテレータで動作する非メンバ関数を作成します。これは#35と結びついています。 STLクラスは実際には基本クラスではありません。 #33は、STLベクタに "読み込み"と "保存"機能を追加したいとコメントしている理由です。そのような機能は、非会員機能でなければならないように聞こえる。

ああ、あなたが本当にこれを通じ思っあります

私は、私は実装を変更した場合、クライアントが再コンパイルする必要はありません確実にするためにプリミティブ型を公開するインターフェイスを作成したいと思います。私はベクトルサイズを整数として公開することを考えていた。

一方で、別のクラスの中のベクトルを隠すために合成を使いたいと思う。はい。最も柔軟な設計ではありませんが、おそらくC + +以外のコードとのインタフェースが必要です。しかし、クラスのデータメンバーをインターフェースの一部として公開したいとします。これにより、クライアントが再コンパイルする必要がある問題が発生する可能性があります。それは意味をなさない。総カプセル化が必要な場合や、頻繁な再コンパイルが気にならない場合。どちらですか? (そして、MFC CArrayクラスで何もモデル化しないでください。ひどいです)

+0

答えをありがとう。私はサイズを公開する必要はありません。それはクライアントが[]演算子を使って配列を反復することを可能にする方法の1つに過ぎませんでした。私は、配列のサイズを表す整数を返すメソッドを見ていました。データメンバを公開していないためです。intを返すさまざまなAPIとそのエントリポイントを見ると、安全で一般的なように見えるからです。 – TownCube

+0

+1メンバー以外の友人以外のアルゴリズムの場合+1。 –

+0

@Cube、STL Vectorには既にsize()というメソッドがあります。 size_typeを返すconstメソッドです。size_typeは一般にsize_tとして定義されています。通常はunsigned intです。 – jmucchiello

0

あなたは「 のプリミティブ型しか公開しないインターフェイスを作成して、クライアントが再コンパイルする必要がないようにしたいと思っています。ちょうど の反対は本当です;再コンパイルを避けたい場合は、 をすべてコンパイルファイアウォールのイディオムを使用してユーザ定義のタイプにする必要があります。 イテレータの場合、これは受け入れられないパフォーマンスを持つ可能性があります。 ペナルティ:イテレータのイディオムにはディープコピーが必要です。また、 は何もインライン化できないため、最適化にも影響する可能性があります。

C++の仕組みを考えれば、再コンパイルを避けることはおそらく妥当でないと思います。さらに重要な点は、実装を変更した場合にクライアントコードに の変更が必要にならないようにすることです。この のケースでは、たとえば、 の保証を正確に定義したいとします。あなたのクラスへの反復子がランダムでなければならないと決めるならば、std::vector<>::iteratorへのtypedefは で十分でしょう;すべてを保証する場合は、順方向反復 (後で実装の自由度を高めるために) は、 をサポートしている操作を公開する の独自のイテレータでstd::vector<>::iteratorをラップすることを検討します。すべての将来のバージョン。

ランダムアクセスイテレータをサポートしていない実装を後で使用する場合は、[]もサポートできないことに注意してください。 []をサポートすると、今後の実装では、単に前方イテレータをサポートする より多くの制約が課せられます。

関連する問題