C++ APIスタイル(フラットC API)
今、「C++のためのAPIデザイン」を読んでます。 API機能を表現するための方法(スタイル)についてまとめてみました。
- 作者: マーティン・レディ,Martin Reddy,三宅陽一郎,ホジソンますみ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2012/11/02
- メディア: 大型本
- 購入: 4人 クリック: 106回
- この商品を含むブログ (11件) を見る
APIスタイルについて・・・
この4つのスタイルのAPIを解説し、状況によって適切なスタイルの 選び方を提示します。
この記事では、以下のAPIスタイルを解説します。
- フラットC API
フラットC API
フラットC APIとは・・・
CコンパイラでコンパイルできるAPI。 自由関数の集合と、それをサポートするデータ構造と定数で構築される。 オブジェクトや継承のないインターフェイスであるため「フラット」と呼ばれる。
C APIの代表例
・標準Cライブラリ Cでプログラムを記述するプログラマにはお馴染みライブラリ。
・Windows API Microsoft Windowsのアプリケーションを開発するために使用するための インターフェイスのコアセット。
・LinuxカーネルAPI LinuxカーネルはすべてプレーンなCで記述されている。
・画像ライブラリ 各種の画像ファイル形式のサポートをアプリケーションに追加する 大半のオープンソース画像ライブラリは、すべてCで記述されている。
ANSI Cの機能
C++に慣れているプログラマがプレーンなCでAPIを記述する場合には 多くの言語機能が使えなくなることに注意が必要です。
- int, float, char, doubleなどの基本型とこれらの配列とポインタ
- typedef, enumキーワードで作成したカスタム型
- struct, unionキーワードで宣言したカスタム構造体
- グローバルな自由関数
defineなどのプリプロセッサ命令
C言語は、C++の厳密なサブセットではありませんが、 多くの場合は優れたANSI CプログラムはC++のプログラムでもあります。
一般に、CコンパイラよりC++コンパイラのほうが型にチェックが厳しいため プレーンなCでAPIを記述する際は、まずC++のコンパイラでコンパイルして 発生する警告やエラーを修正するのも効果的です。
C APIのコンパイルにC++のコンパイルを使うと厳しい型チェックが行われ C++でも使えるAPIになる。
ANSI Cの利点
C言語でAPIを記述する主な理由は、完全にCで記述された既存のプロジェクトと 統合できる事です。 先程、示したように大規模なCのAPIは今日でも使われています。 例えば、LinuxカーネルAPIを使っている場合はCで記述する必要があります。
もちろん、CとC++の双方で動くAPIを作る事も出来ます。 C++記述によってオブジェクト指向の機能を活用する一方で、このインターフェイスの プレーンなCのラッパーを構築してC言語のみプロジェクトで使ったりする事も可能です。
ANSI CによるAPIの記述
C言語ではクラスがサポートされてされていないため、処理を行うメソッドと共に データをオブジェクトにカプセル化することは出来ません。 その代わり、データを格納する構造体を宣言してデータを処理する関数に渡します。
以下のC++のクラスの定義を見ていきましょう。
class Stack { public: void Push(int value); int Pop(); bool IsEmpty() const; private: int *stack_; int current_size_; };
これをフラットなCのAPIにすると以下のようになります。
struct Stack { int *stack_; int current_size_; }; void StackPush(struct Stack *stack, int value); int StackPop(struct Stack *stack); bool StackIsEmpty(struct Stack *stack);
スタックに関するCの関数は、データ構造のStackをパラメータとして 受け取る必要があります。
また、関数名はC++のようにクラス宣言で名前空間を定める事が出来ないため、 操作対象のデータを示す文字を含める事が多いです。 ここでは、Stackを接頭辞として付けてデータ構造のStackを操作する 関数であることを明確にしています。
以下のように、オペークポインタを使ってプライベートデータを隠蔽することも可能です。
typedef struct Stack *StackPtr void StackPush(StackPtr stack, int value); int StackPop(StackPtr stack); bool StackIsEmpty(StackPtr stack);
また、Cはコンストラクタやデストラクタをサポートしていないため、 クライアントが明示的に初期化し、破棄しなくてはいけません。 これを行うためには、データ構造の作成と破棄のAPIを追加します。
StackPtr StackCreate();
void StackDestroy(StackPtr stack);
さて、同じタスクを行うC APIとC++ APIのコードを比較したので、 今度は、それぞれのAPIスタイルを使うためのクライアントの記述を見ていきます。
Stack *stack = new Stack(); if (stack) { stack->Push(10); stack->Push(3); while (!stack->IsEmpty()) { stack->Pop() } delete stack; }
次のC APIは以下のようなります。
StackPtr stack = StackCreate(); if (stack) { StackPush(stack, 10); StackPush(stack, 3); while (!StackIsEmpty(stack)) { StackPop(stack); } StackDestroy(stack); }
C++からのC関数の呼び出し
C++コンパイラはCコードをコンパイルする事も出来ます。 現在の開発ではC APIだからといってC++コンパイラを使わない手はありません。 しかも、比較的簡単な作業なので、C APIをリリースする際には行って損はありません。
最初に確認すべきは、コンパイル出来るかどうかです。 先に述べた通り、C言語の規格はC++より厳しくないためC++コンパイルで比べると、 Cコンパイラは不注意なコードでもコンパイル出来る可能性は高いです。
この作業では、コード内にC++の予約名が使われていないかの確認も必要です。 例えば、以下のコードはCでは正しいですが、C++ではエラーになります。 理由はC++では[class]がC++では予約語だからです。
enum class {RED, GREEN, BLUE};
最後にCの関数はC++とはリンゲージが異なります。 つまり、CコンパイラとC++コンパイが作成したオブジェクトファイルでは、 同じ関数でも表現が異なります。 これはC++でサポートされたオーバーロードが原因の一つです。 その結果、C++の関数名は、パラメータや個数や型などの追加情報を 符号化するために「マングル」されます。
例えば、DoAction()というか関数を使ったC++コードをコンパイルしても、 DoAction()関数をを定義するCコンパイラで生成したライブラリにリンク出来ません。
この問題を切り抜けるには、extern"C"構造にC APIをラップして、 格納された関数にはCスタイルのリンゲージを使うように、C++コンパイラに明示します。 このステートメントはCコンパイラではパース出来ないため、C++コンパイラのみで 条件付きコンパイルしましょう。
#ifdef __cplusplus extern "C" { #endif // あなたのC API宣言 #ifdef __cplusplus } #endif
C APIのヘッダにextern "C"スコープを使って、C++があなたのAPIを正しくコンパイルとリンクが出来るようにしよう。