0Pricing
C++ Academy · レッスン

CRTPイディオム

Curiously Recurring Template Pattern

「CRTPイディオム」はCoddyKit上の無料C++ Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC++ Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C++ Academyコースには全4レッスンが含まれています。

CRTPとは

Curiously Recurring Template Pattern(CRTP)は、クラスDerivedがDerived自身を使ってインスタンス化したテンプレート基底クラスを継承するC++のイディオムです。

  • 形:class Derived : public Base<Derived>
  • 基底クラスはコンパイル時に派生型を認識できます
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

中核となる仕組み

基底クラス内では、thisポインタを派生型へstatic_castできます。派生型はテンプレートパラメータなので、これは実行時コストのない完全なコンパイル時キャストです。

#include <iostream>

template <typename T>
class Base {
public:
    void interface() {
        static_cast<T*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() { std::cout << "Derived impl\n"; }
};

int main() {
    Derived d;
    d.interface();
    return 0;
}

なぜ「Curiously Recurring」なのか

この名前は一見矛盾しているように見えることに由来します。基底クラスは、基底テンプレートを記述した時点ではまだ完全に定義されていない派生クラスを参照するためです。

  • テンプレートは使用時にだけインスタンス化されるため機能します
  • インスタンス化される時点では、Derivedは完全型になっています

最初の実用例

CRTPを使うと、派生クラスを呼び戻す再利用可能な機能を基底クラスに提供できます。ここでは基底クラスがprint()を提供し、それが派生クラスのname()に処理を委譲します。

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

仮想テーブルを使わない

実行時ポリモーフィズムとは異なり、CRTPではvirtualキーワードも、各オブジェクト内の仮想テーブルポインタも必要ありません。ディスパッチはすべてコンパイル時に解決されます。

  • オブジェクトが小さくなります(隠れたvptrがありません)
  • 呼び出しをインライン化できます

インスタンスを数える

CRTPの典型的な用途はオブジェクトカウンタです。派生クラスごとに独自のstaticカウンタが自動的に用意されます。

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

派生クラスごとに独自の状態を持つ

Counter<Widget>とCounter<Gadget>は異なるテンプレートインスタンス化であるため、それぞれが独立したstaticなcountを持ちます。単一の非テンプレート基底クラスでは、この分離は不可能です。

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

ここではstatic_castは安全

基底クラス内のダウンキャストが安全なのは、Base<T>型のすべてのオブジェクトが実際にTである場合だけです。CRTPでは、継承するクラスがTになるため、構造上この条件が保証されます。

  • 無関係なクラスに対してBase<Wrong>をインスタンス化してはいけません

CRTPによる比較演算子

比較用ヘルパーを継承することで、1つのoperator<から比較演算子一式を生成できます。

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

protectedコンストラクタによる保護

基底クラスの誤用を防ぐには、protectedコンストラクタを用意し、派生クラスだけが基底クラスを生成できるようにします。

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

class Job : public Base<Job> {
public:
    void step() { std::cout << "step\n"; }
};

int main() {
    Job j;
    j.run();
    return 0;
}

標準ライブラリでのCRTP

CRTPは実際のC++ライブラリ全体で使われています。

  • std::enable_shared_from_this<T>が使用しています
  • 多くの式テンプレートを使う数学ライブラリ(Eigen)が依存しています
  • Rangesやイテレータヘルパーも同様のパターンを使っています

X : Base<X>という形を認識できると、高度なコードを読みやすくなります。

クイックチェック

CRTPの形を理解できているか確認しましょう。

まとめ

CRTPのイディオムを学びました。

  • Derived : Base<Derived>によって、基底クラスに具体的な型を伝えます
  • static_cast<T*>(this)によってコンパイル時ディスパッチを実現します
  • 仮想テーブルがなく、オブジェクトが小さく、呼び出しをインライン化できます
  • カウンタ、比較ミックスイン、ライブラリヘルパーに使用されます

よくある質問

「CRTPイディオム」レッスンは無料ですか?

はい。「CRTPイディオム」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C++ Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C++ Academyコースには全4レッスンが含まれています。

「CRTPイディオム」で何を学びますか?

Curiously Recurring Template Pattern ブラウザで直接実行するハンズオンコードでC++ Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C++ Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC++ Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「CRTPイディオム」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC++ Academyレッスンでコードを書いて実行できますか?

はい。すべてのC++ Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. CRTPイディオム
  2. 静的ポリモーフィズム
  3. CRTPによるミックスイン
  4. CRTPを使うタイミング
← C++ Academyに戻る