読者です 読者をやめる 読者になる 読者になる

ゲーム業界エンジニアの独り言

クライアント、サーバ、インフラなどなど興味あることなんでも紹介

C++ APIスタイル(テンプレートベースのAPI)

今、「C++のためのAPIデザイン」を読んでます。 API機能を表現するための方法(スタイル)についてまとめてみました。

C++のためのAPIデザイン

C++のためのAPIデザイン

APIスタイルについて・・・

この4つのスタイルのAPIを解説し、状況によって適切なスタイルの 選び方を提示します。

この記事では、以下のAPIスタイルを解説します。

  • テンプレートベースのAPI

テンプレートベースのAPI

テンプレートベースのAPIとは・・・

テンプレートとは、型を特定する前のジェネリックな型に基づいて 関数とクラスを記述できるC++の機能です。 テンプレートを記述すると、後で特定の型にインスタンス化できます。 そこで、テンプレートを使ったプログラミングはジェネリックプログラミングと呼ばれます。

テンプレートAPIの代表例

標準テンプレートライブラリ(STL std::set, std::map, std::vectorといった馴染みのあるSTLコンテナクラスは テンプレートであり、そのため異なる型のデータを格納できる。

Boost 強力かつ有用な機能を集めたライブラリ。その機能の多くがC++0xに組み込まれる予定。 boost::shared_ptr, boost::function, boost::pointer_castといった Boostクラスの大半がテンプレート機能を使っている。

Loki アンドレイ・アレキサンドレスクが自署「Modern C++ Design」を サポートするために記述したクラステンプレートのライブラリ。 ビジター、シングルトン、ファクトリといったデザインパターンの実装を提供している。 優れたテンプレートベースAPIの設計の手本となるエレガントなコード。

テンプレートAPIの例

スタックの例を使って、テンプレート機能を使ったジェネリックな スタック宣言を作成し、整数用にインスタンス化を行ってみます。

テンプレートクラスのスタック定義はジェネリック型のTに基づいて 以下のように定義できます。

#include <vector>

template<T>
class Stack
{
public:
    void Push(T value);
    T Push();
    bool IsEmpty();

private:
    std::vector<T> stack_;
};

Tという名前に特別な意味はありません。 ジェネリック型にはTを使うのが一般的ですが、どんな名前にしても問題ありません。

ジェネリックなスタックを宣言したので、Stack型のオブジェクトを作成して int型用にテンプレートをインスタンス化できます。

Stack<int> stack;

さらに、シンプルなtypedeを定義すれば、以下のようにテンプレートの このインスタンスにアクセスしやすくなります。

typedef Stack<int> IntStack;

このIntStack型は、明示的にクラスを記述したかのように使用できます。

IntStack *stack = new IntStack();
if (stack)
{
    stack->Push(10);
    stack->Push(3);
    while (!stack->IsEmpty())
    {
        stack->Pop()
    }
    delete stack;
}

テンプレートとマクロ

テンプレート機能に代わる手法として、C言語プリプロセッサで テキストブロックを定義して、ヘッダに何度も挿入することが出来ます。

#include <vector>

#define DECLARE_STACK(Prefix, T) ¥
class Prefix##Stack ¥
{ ¥
public: ¥
    void Push(T value); ¥
    T Push(); ¥
    bool IsEmpty(); ¥
¥
private: ¥
    std::vector<T> stack_; ¥
};

DECLARE_STACK(Int, int);

コードが醜くなり(プリプロセッサの連結にのため各行の終わりが円記号になる)、 プリプロセッサには、型のチェックやスコープ化の概念はないため、 テキストをコピーするメカニズムでしかありません。

マクロ宣言は実際にはコンパイルされないため、マクロにエラーがあっても 展開した各行に展開されるのであって、宣言された場所にはされません。 同様に、デバッカでメソッドをステップ実行することは出来ません。

これとは対照的に、テンプレートを使えば、コンパイル時に型セーフな コードがコンパイルできます。 しかも、クラステンプレートの実際の行ごとにデバッグできます。

要約すると、プレーンなCでAPIを記述している以外は、 テンプレートが使えないわけではないので、プリプロセッサでテンプレートを 真似る事は避けるべきです。

テンプレートAPIのデメリット

テンプレートを使った場合のデメリットは以下になります、

クラステンプレートの定義がパプリックヘッダに書かなければいけない これは特殊化するために、コンパイラがテンプレート全体にアクセスを する必要があるからです。 これによって内部情報を開示する必要があるため、API開発においては大罪です。

コンパイル時間がの増加、コード量の増加 ファイルがインクルードされるたびに、インラインコードを 再コンパイルする必要があるため、このAPIを使用するモジュールごとに 生成したコードがオブジェクトファイルに追加されます。 結果的に、コンパイル時間が長くなったり、コード量がふくれ上がったりします。

テンプレート内で発生する膨大なエラーメッセージ 大半のコンパイラがテンプレートコード内で発生するエラーについて、 冗長で混乱を招くエラーメッセージを発生させます。 テンプレートを大量に使ったために、シンプルなエラーなのに膨大な エラーメッセージが発生し頭を悩ませる事は、少なくありません。 これはAPI開発者だけの問題ではなく、API使用するクライアントにも降り掛かります。