C++クラステンプレートと継承の設計パターンと型解決

テンプレート基底クラスと非テンプレート派生クラスの組み合わせ

基底側がパラメータ化されたクラステンプレートであり、派生側が具体的なデータ型に依存しない通常のクラスである場合、継承宣言の時点で基底クラスの型引数を確定する必要があります。コンパイラは派生クラスの定義段階で基底クラスのメモリレイアウトを解決するため、テンプレート引数の具体値を省略することはできません。

#include <iostream>

template <typename T>
class DataRepository {
public:
    explicit DataRepository(T val) : primaryKey(val) {}
    T fetchPrimary() const { return primaryKey; }
private:
    T primaryKey;
};

// 派生クラスは通常クラスとして定義
class SpecializedCache : public DataRepository<int> {
public:
    SpecializedCache(int core, int ext) 
        : DataRepository<int>(core), extension(ext) {}
    
    int fetchExtension() const { return extension; }
private:
    int extension;
};

int main() {
    SpecializedCache cacheObj(100, 500);
    std::cout << cacheObj.fetchPrimary() << " : " << cacheObj.fetchExtension() << "\n";
    return 0;
}

上記の実装において、継承リスト class SpecializedCache : public DataRepository<int> で明示的に int を指定している点が重要です。これにより派生クラスは基底の型パラメータをバインドします。コンストラクタの初期化子リスト内では、既に型が確定しているため SpecializedCache(int core, int ext) : DataRepository(core), extension(ext) {} のように型指定を省略しても問題なく動作しますが、宣言段階での型明示は必須です。

非テンプレート基底クラスとテンプレート派生クラスの組み合わせ

この構成では、基底クラスは固定された型構造を持ち、派生クラスのみがジェネリックな振る舞いを担います。継承のメカニズム自体は通常のクラス継承と全く同一であり、テンプレート構文は派生クラスのメンバ変数やメンバ関数にのみ影響を与えます。

#include <iostream>

class FixedEntity {
public:
    explicit FixedEntity(int id) : entityID(id) {}
    int getIdentifier() const { return entityID; }
private:
    int entityID;
};

template <typename Payload>
class DynamicWrapper : public FixedEntity {
public:
    DynamicWrapper(int id, Payload data) 
        : FixedEntity(id), payloadContent(data) {}
    
    Payload getPayload() const { return payloadContent; }
private:
    Payload payloadContent;
};

int main() {
    DynamicWrapper<std::string> wrapper(1, "ConfigAlpha");
    std::cout << wrapper.getIdentifier() << " - " << wrapper.getPayload() << "\n";
    return 0;
}

派生クラスがテンプレート化されているため、インスタンス生成時に型推論または明示的な型指定が必要となります。基底クラス側はジェネリックプログラミングの関知しない範囲であるため、実装や利用における制約は生じません。

双方がクラステンプレートである場合の型伝播

基底クラスと派生クラスの両方がテンプレートパラメータを持つ場合、派生クラスはそのパラメータを自身の内部ロジックに使用するか、基底クラスへ転送(Forward)するかを選択できます。この場合、継承リストにおいて基底クラスの型引数として「具体的な型」または「派生クラスで定義したテンプレート仮引数」のいずれかを指定しなければなりません。

#include <iostream>

template <typename RootType>
class GenericFoundation {
public:
    explicit GenericFoundation(RootType r) : foundationVal(r) {}
    RootType accessFoundation() const { return foundationVal; }
private:
    RootType foundationVal;
};

// 派生側もテンプレートであり、基底へ型を伝播させる
template <typename BranchType>
class GenericDerivation : public GenericFoundation<BranchType> {
public:
    GenericDerivation(BranchType r, BranchType b) 
        : GenericFoundation<BranchType>(r), branchVal(b) {}
        
    BranchType accessBranch() const { return branchVal; }
private:
    BranchType branchVal;
};

int main() {
    GenericDerivation<double> hierarchy(3.14, 2.71);
    std::cout << hierarchy.accessFoundation() << " / " << hierarchy.accessBranch() << "\n";
    return 0;
}

重要なルールとして、C++コンパイラはクラス継承の宣言時点で基底クラスの完全な型情報を必要とします。したがって class GenericDerivation : public GenericFoundation<BranchType> のように、派生クラスの仮引数を基底の型実引数として渡す構文が必須となります。これにより、テンプレートクラスが別のテンプレートクラスの基底として機能し、階層的なジェネリック構造を安全に構築することが可能になります。インスタンス化時には最上位の型パラメータが一度に決定され、すべての階層に適用されるため、型安全性が維持されます。

タグ: C++ class-templates inheritance generic-programming type-deduction

5月11日 18:34 投稿