9

次の 2 つの使用シナリオを考慮してください (ご覧のとおり、つまり、エンド ユーザーは と の使用のみに関心がVector2_tありますVector3_t)。

[1]継承:

template<typename T, size_t N> struct VectorBase
{
};

template<typename T> struct Vector2 : VectorBase<T, 2>
{
};

template<typename T> struct Vector3 : VectorBase<T, 3>
{
};

typedef Vector2<float> Vector2_t;
typedef Vector3<float> Vector3_t;

[2]専門:

template<typename T, size_t N> struct Vector
{
};

template<typename T> struct Vector<T, 2>
{
};

template<typename T> struct Vector<T, 3>
{
};

typedef Vector<float, 2> Vector2_t;
typedef Vector<float, 3> Vector3_t;

どちらがより良い解決策であるかについて、私は決心できません。継承の明らかな利点は、派生クラスでのコードの再利用です。パフォーマンスが不利になる可能性があります (サイズが大きい、ユーザーが値を渡す可能性があるなど)。専門化はそれをすべて回避しているように見えますが、私は自分自身を何度も繰り返さなければならないという犠牲を払っています.

他に見逃した長所/短所はありますか? また、あなたの意見では、どのルートを選択する必要がありますか?

4

4 に答える 4

12

あなたが最終的に望むのは、ユーザータイプを持つことだと思います

Vector<T, N>

に応じてN、ユーザーはわずかに異なるものを取得します。1 つ目はそれを実現しませんが、2 つ目はコードの重複を犠牲にして実現します。

あなたができることは、継承を逆にすることです:

template<typename T, size_t N> struct VectorBase 
{
};

template<typename T> struct VectorBase<T, 2>
{
};

template<typename T> struct VectorBase<T, 3>
{
};

template<typename T, size_t N> struct Vector : VectorBase<T, N>
{
};

そして、適切な基本クラスの特定の値である N のみに依存するいくつかの関数を実装します。Vectorそれらに保護されたデストラクタを追加して、ユーザーがへのポインターを介してのインスタンスを削除するのを防ぐことができますVectorBase(通常、ユーザーは に名前を付けることさえできませんVectorBase。これらのベースを のような実装名前空間に配置しますdetail)。

別のアイデアは、このソリューションを別の回答で言及されているソリューションと組み合わせることです。(上記のようにパブリックではなく) プライベートに継承し、基本クラスの実装を呼び出す派生クラスにラッパー関数を追加します。

さらに別のアイデアは、クラスを 1 つだけ使用してからenable_if( を使用してboost::enable_if) の特定の値に対してそれらを有効または無効にするかN、このような int から型へのトランスフォーマーを使用することです。

struct anyi { };
template<size_t N> struct i2t : anyi { };

template<typename T, size_t N> struct Vector
{
    // forward to the "real" function
    void some_special_function() { some_special_function(i2t<N>()); }

private:
    // case for N == 2
    void some_special_function(i2t<2>) {
        ...
    }

    // case for N == 3
    void some_special_function(i2t<3>) {
        ...
    }

    // general case
    void some_special_function(anyi) {
        ...
    }
};

そうすれば、 のユーザーに対して完全に透過的になりますVector。また、空の基底クラスの最適化 (非常に一般的) を行うコンパイラにスペース オーバーヘッドを追加することもありません。

于 2009-04-07T00:13:13.693 に答える
4

継承とプライベート継承を使用します。また、仮想関数を使用しないでください。プライベート継承では is-a がないため、誰も派生サブクラスへの baseclas ポインターを使用できず、値渡し時にスライシングの問題は発生しません。

これにより、両方の長所が得られます (実際、ほとんどのライブラリが多くの STL クラスを実装する方法です)。

http://www.hackcraft.net/cpp/templateInheritance/から( Vectorクラスではなく、std::vector について説明します):

vector<T*>は のプライベート ベースを持つと宣言されていvector<void*>ます。新しい要素をベクトルに配置するすべての関数 ( など) はpush_back()、このプライベート ベースで同等の関数を呼び出すため、内部的にはストレージにvector<T*>を使用して います。vector<void*>ベクトルから要素を返すすべての関数 ( など) は、プライベート ベースで同等の関数を呼び出した結果に対して をfront()実行します。(故意に危険なトリックを除いて)static_castにポインターを取得する唯一の方法は、 によって提供されるインターフェイスを使用することであるため、 back をに静的にキャストしても安全です (またはback を にキャストするなど)。vector<void*>vector<T*>void*T*void*&T*&

一般に、STL がこのように動作する場合、エミュレートするのに適切なモデルのように思えます。

于 2009-04-07T00:10:14.777 に答える
0

継承は、「is-a」をモデル化するためにのみ使用する必要があります。専門化は、よりクリーンな代替手段になります。なんらかの理由で継承を使用する必要がある場合、または使用したい場合は、少なくともプライベートまたは保護された継承にして、パブリックの非仮想デストラクタを持つクラスからパブリックに継承しないようにします。

はい、テンプレートのメタプログラミング担当者は常にそうしています

 struct something : something_else {};

しかし、これらsomethingメタ関数であり、型として使用するためのものではありません。

于 2009-07-29T09:05:14.510 に答える
-1

テンプレートの特殊化を使いすぎている場合は、おそらく設計を再考する必要があります。の後ろに隠していることを考えると、typedef必要だとは思えません。

于 2009-04-07T00:08:53.780 に答える