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

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

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

findコマンドのmtimeオプションまとめ

findコマンドの時間指定は色々パターンがあるので、 まとまてみました。


時間指定オプション一覧。

オプション 概要
-mmin ファイルのデータが最後に修正された日時(分指定)
-mtime ファイルのデータが最後に修正された日時(日指定)
-amin ファイルのデータに最後にアクセスされた日時(分指定)
-atime ファイルのデータに最後にアクセスされた日時(日指定)
-cmin ファイルのデータとステータスが最後に修正された日時(分指定)
-ctime ファイルのデータとステータスが最後に修正された日時(日指定)

3日前まで(現在時間〜72時間前)

# find ./ -mtime -3

3日前(72時間前〜96時間前)

# find ./ -mtime 3

3日前以降(72時間前〜過去)

# find ./ -mtime +3

daystartオプションを指定すると0時を基準にします。 1日単位で範囲指定したい場合に使用します。

例として、現在を12月24日03時00分とします。

3日前まで(12月24日00時00分〜72時間前)

# find ./ -daystart -mtime -3

3日前(12月21日00時00分〜24時間前)

# find ./ -daystart -mtime 3

3日前以降(12月21日00時00分〜過去)

# find ./ -daystart -mtime +3

少数指定も出来るみたいです。

12時間前まで(現在時間〜12時間前)

# find ./ -mtime -0.5

Linux逆引き大全 555の極意 コマンド編 [第2版]

Linux逆引き大全 555の極意 コマンド編 [第2版]

APIのプラグインによる拡張性

今、「C++のためのAPIデザイン」を読んでます。 APIを使うユーザが、APIを拡張するため必要なプラグインについてまとめてみました。

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

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

目次

APIの拡張性とは?

拡張性とは、APIを使うユーザがAPI開発者の手を借りずに、 特定のニーズ用に、インターフェイスの振る舞いを変更出来る事です。 拡張性があれば、API開発者は焦点を定めた無駄のないインターフェイスに、 集中することが出来ます。

その一方、ユーザにはAPI開発者が予想もしなかった問題を解決できる、 柔軟性を与える事が出来るのです。

プラグインによる拡張

プラグインの最も一般的な方法は、APIライブラリ構築時に アプリケーションにリンクするダイナミックライブラリではなく、 実行時に認識され、ロードさせるダイナミックライブラリです。

そこで、ユーザに分かりやすいプラグインAPIを提供すれば、 ユーザがプラグインを記述し、ユーザはAPI開発者が指定した方法で、 機能を拡張する事が出来るのです。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー API    APIコード    → コンパイラ → APIライブラリ  ーーーーーーーーーーーーーーーーーーーーーーーー↓ーーーーーー    プラグインコード → コンパイラ → プラグインライブラリ  プラグイン ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

プラグインライブラリはコアAPIとは別にコンパイル出来ます。 APIによって要求に応じてロードされるダイナミックライブラリです。

APIプラグインを採用する利点

ソフトウェアのパッケージ製品の多くが、C/C++プラグインで コア機能の拡張が可能です。

APIプラグインを採用する利点を以下に述べます。

・用途の拡張 APIが解決できる問題は広域に及ぶ可能性があります。 しかし、プラグインがあれば、全てのソリューションを実装する必要はありません。

・コミュニュティの発展 APIフレームワーク内で、ユーザに問題解決できる方法を与えると、 ユーザの貢献でAPIが拡張される、コミュニュティが構築出来る。

・更新の小容量化 プラグインで作られた機能は、新バージョンのプラグインを更新するだけで、 アプリケーションとは別に、簡単に更新することが出来ます。 アプリケーション全体の新バージョンを配布するより、ずっと小さい更新で済みます。

・将来性の確保 APIはある時点で、これ以上の更新が必要ない安定したレベルに達するものです。 そこで、プラグインの開発によって機能を進化させ続ければ、APIの有用性や、 重要性を長時間保つ事が出来ます。

・リスクの分散 プラグインを使うと、コアシステムを不安定にさせず、機能を変更する事が出来ます。 そのため、社内開発でもメリットがあります。

プラグインシステムの設計

プラグインを設計する方法はいくつもあります。 最善のソリューションはプロジェクトごとに異なります。

一般に、プラグインシステムを構築する際に、設計すべき機能は2つです。

1.プラグインAPI プラグインを作成するために、ユーザがコンパイルとリンクを行う対象のAPIです。 プラグインシステムで使用する、大きなコードベースのコアAPIとは区別します。

2.プラグインマネージャプラグインのライフサイクルを管理(ロード、登録、解放)する コアAPIコード内のオブジェクト(通常はシングルトン)です。 このオブジェクトはプラグインレジストリとも呼ばれます。

ーーーーーーーーーーーーーーーーーーーーーー コアAPI              プラグインマネージャ ーーーーー↓ーーーー↓ーーーーー↓ーーーーー  プラグイン1 プラグイン2 プラグイン3 プラグイン ーーーーーーーーーーーーーーーーーーーーーー

プラグインマネージャはプラグインを認識してロードします。

C++プラグイン実装

ここで説明するプラグインシステムでは、ファクトリメソッドを提供して、 C++のクラスをコアAPIに登録出来るようにします。 ユーザが3Dフラフィックレンダラのプラグインを作成し、コアAPIに登録します。

プラグインAPI

プラグインAPIはユーザーがプラグインを作成できるようにするための、 インターフェイスです。 ここでのファイル名は"pluginapi.h"とします。 これには、プラグインがコアAPIと通信するための機能が格納されます。

// pluginapi.h
#include "defines.h"
#include "renderer.h"

#define CORE_FUNC extern "C" CORE_API
#define PLUGIN_FUNC extern "C" PLUGIN_API

#define PLUGIN_INIT() PLUGIN_FUNC int PluginInit()
#define PLUGIN_FREE() PLUGIN_FUNC int PluginFree()

typedef IRender *(*RendererInitFunc)();
typedef void (*RendererFreeFunc)(IRender *);

CORE_FUNC void RegisterRenderer(
    const char *type,
    RendererInitFunc init_cb,
    RendererFreeFunc free_cb);

このヘッダではプラグイン用に、初期化関数とクリーンナップ関数を マクロで定義しています。 それぞれ、PLUGIN_INIT()とPLUGIN_FREE()です。

また、PLUGIN_FUNCマクロではコアAPIが呼び出せるように、 プラグイン関数を、エクスポートさせます。 逆に、CORE_FUNCマクロでは、プラグインが呼び出せるように、 コアAPI関数をエクスポートさせています。

最後に、RegisterRenderer()関数を提供し、プラグインに 新しいIRendererクラスをコアAPIに登録できるようにしました。

プラグインが新しいIRendererクラス用にInit()関数とFree()関数を 提供する必要があります。 これはプラグイン内で、メモリの割り当てと解放を確実に行わせるためです。

また、CORE_APIとPLUGIN_APIの定義にも注目してください。 これでWindows上で、正しいDLLエクスポート/インポートの修飾子が指定出来ます。 CORE_APIはコアAPIの一部の関数を修飾し、PLUGIN_APIプラグイン内で、 定義されることになる関数に使われます。 これらのマクロは"defines.h"に格納されています。

// defines.h
#ifdef _WIN32
#ifdef BUILDING_CORE
#define CORE_API __declspec(dllexport)
#define PLUGIN_API __declspec(dllimport)
#else
#define CORE_API __declspec(dllimport)
#define PLUGIN_API __declspec(dllexport)
#endif
#else
#define CORE_API
#define PLUGIN_API
#endif

これらのマクロを、正しく機能させるには、BUILDING_CORE定義セットで ビルドする必要があることに注意してくだい。 なお、この定義はプラグインコンパイルする時には必要ありません。

最後に、拡張する3Dフラフィックレンダラの基礎クラスが定義されている、 "renderer.h"のファイル内容を示します。

// renderer.h
#include <string>

class IRenderer
{
public:
    virtual ~IRenderer() {}
    virtual bool LoadScene(const char *file_name) = 0;
    virtual void SetViewportSize(int w, int h) = 0;
    virtual void SetCameraPosition(double x, double y, double z) = 0;
    virtual void SetLookAt(double x, double y, double z) = 0;
    virtual void Render() = 0;
};

プラグインのコード例

初歩的なプラグインAPIを開発したので、このAPIに対して構築したプラグインが、 どのようなものかを検証していきます。

ここに含めるべきパーツは以下の通りです。

1.新しいIRendererクラス 2.このクラスの作成と破棄を行うコールバック 3.作成/破棄コールバックをコアAPIに登録するプラグイン初期化ルーチン

このようにしたプラグインのコードを以下に示します。 このプラグインは「opengl」という新しいレンダラを定義し登録しています。

// plugin1.cpp
#include "pluginapi.h"
#include <iostream>

class OpenGLRenderer : public IRenderer
{
public:
    virtual ~OpenGLRenderer() {}
    virtual bool LoadScene(const char *file_name) { return true; }
    virtual void SetViewportSize(int w, int h) {}
    virtual void SetCameraPosition(double x, double y, double z) {}
    virtual void SetLookAt(double x, double y, double z) {}
    virtual void Render() { std::cout << "OpenGL Renderer" << std::endl; }
};

PLUGIN_FUNC IRenderer *CreateRenderer()
{
    return new OpenGLRenderer();
}

PLUGIN_FUNC void DestroyRenderer(IRenderer *r)
{
    delete r;
}

PLUGIN_INIT()
{
    RegisterRenderer("opengl", CreateRenderer, DestroyRenderer);
    return 0;
}

プラグインマネージャ

プラグインAPIと、このAPIに対するプラグインを構築したので、 次はこれらのプラグインをコアAPIにロードして登録します。 これらはプラグインマネージャの仕事です。

プラグインマネージャの具体的なタスクを以下に示します。

・全てのプラグインメタデータをロード 全てのプラグインメタデータをロードします。 メタデータは別ファイルに格納したり(XMLプラグイン自体に含んだりします。 このメタデータによって、使用可能なプラグインのリストが表示され、 ユーザは必要なプラグインを選択する事が出来ます。

・ダイナミックライブラリのロード ダイナミックライブラリをメモリにロードし、このライブラリの シンボルへのアクセスを提供します。必要であればアンロードも行います。 (これらの詳細な方法についてはここで説明しません。)

プラグインの初期化ルーチンとクリーンナップルーチンの呼び出し プラグインがロードされた時に、プラグインの初期化ルーチンを呼び出し、 プラグインがアンロードされた時に、クリーンナップルーチンを呼び出します。

プラグインマネージャは、システム内の全てのプラグインにアクセスするための、 窓口を提供するため、シングルトンで実装される事が多いです。

では、以下にプラグインマネージャの実装例を示します。

// pluginmanager.cpp
#include "defines.h"
#include <string>
#include <vector>

class CORE_API PluginInstance
{
public:
    explict PluginInstance(const std::string &name);
    ~PluginInstance():
    bool Load();
    bool Unload();
    bool IsLoaded();
    std::string GetFileName();
    std::string GetDisplayName();

private:
    PluginInstance(const PluginInstance &);
    const PluginInstance &operator =(const PluginInstance &);
    class Impl;
    Impl* mImpl;
};

class CORE_API PluginManager
{
public:
    static PluginManager &GetInstance();
    bool LoadAll();
    bool Load(const std::string &name);
    bool UnloadAll();
    bool Unload(const std::string &name);
    bool IsLoaded();
    std::vector<PluginInstance *> GetAllPlugins();

private:
    PluginManager();
    ~PluginManager();
    PluginManager(const PluginManager &);
    const PluginManager &operator =(const PluginManager &);
    std::vector<PluginInstance *> mPlugins;
};

プラグインメタデータは別ファイルに定義します。 以下にメタデータファイル(XML)の例を示します。

<?xml version="1.0" encoding='UTF-8' ?>
<plugins>
    <plugin filename="oglplugin">
        <name>OpenGL Renderer</name>
    </plugin>
    <plugin filename="dxplugin">
        <name>DirectX Renderer</name>
    </plugin>
    <plugin filename="mesaplugin">
        <name>Mesa Renderer</name>
    </plugin>
</plugins>

以下のコードは使用可能な全てのプラグイン名を表示します。

std::vector<PluginInstance *> plugins = 
    PluginManager::GetInstance().GetAllPlugins();

std::vector<PluginInstance *>::iteretor it;
for (it = plugins.begin(); it != plugins.end(); ++it)
{
    PluginInstance *pi = *it;
    std::cout << "Plugin: " << pi->GetDisplayName() << std::endl;
}

ダイナミックライブラリの作成と呼び出し方法は、次回説明しようと思います。

C++テンプレートテクニック

今、「C++のためのAPIデザイン」を読んでます。 テンプレートを使用する際の、テクニックについてまとめてみました。

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

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

目次

テンプレートとは?

テンプレートとは、コンパイル時にコードを生成する機能です。 型だけが異なる大量のコードを、生成する時など特に役に立ちます。

良く知られている例として、C++のコンテナクラスがあります。

vector<int> int_vector; // int型を扱うVectorクラスのインスタンス
vector<float> float_vector; // float型を扱うVectorクラスのインスタンス
vector<string> string_vector; // string型を扱うVectorクラスのインスタンス

テンプレートの用語

テンプレートはC++の機能の中でも、誤解されることが多い機能です。 そこで、テンプレートに関する用語の定義から説明します。

以下のテンプレート定義を使って、用語を定義していきます。

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

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

インスタンス化します。

Stack<int> int_stack;
Stack<string> string_stack;

このクラステンプレートは、要素Tの型を格納できる、一般的なスタッククラスです。

テンプレートパラメータ templateキーワードの後に記載される名前。 例えば、Tは上のスタックの例で指定された、テンプレートパラメータです。

テンプレート引数 特殊化の過程でテンプレートパラメータと置き換わる実体。 Stackという場合は、intがテンプレート引数です。

特殊化

テンプレートがインスタンス化されて、結果として生成される クラス、メソッド、関数は特殊化と呼ばれます。

とはいえ、特殊化という用語は、テンプレートパラメータ対して 具体的な型を特定する事で、関数にカスタム実装を提供する時にも使われます。

template <>
void Stack<int>::Push(int value)
{
    // int型専用のPushを実装
}

このように、具体的な型を特定してメソッドを定義する事で、 その型で特殊化された際の、専用メソッドが定義出来ます。

部分的な特殊化

部分的な特殊化は、元となる汎用なテンプレート引数のうちの、 一部だけを特殊化する方法です。

プログラマが1つの機能を特殊化し、他の機能の指定はユーザーに許可します。

複数パラメータを受け取るテンプレートの例

このパラメータ一つに対して、具体的な型を指定して一つのケースを定義する事で、 部分的な特殊化を行う事が出来ます。

下記の例では、Setの一番目のパラメータをintで作成した際に、 特別な処理を記述する事が可能になります。

template <typename KEY, VALUE>
class Set
{
    // 共通の処理
    bool Insert(KEY key, VALUE value);
};
template <typename VALUE>
class Set<int, VALUE>
{
    // Set<int, VALUE>で特殊化された際の特別処理
    bool Insert(int key, VALUE value);
};

単一パラメータを受け取るテンプレートの例

単一のテンプレートパラメータを持つStackの例では、 テンプレートを部分的に特殊化して、任意の型Tへのポインタを 厳密に処理する事が可能です。

下記の例では、ユーザーがポインタのStackを作成する際に、 特別な処理を記述する事が可能になります。

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

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

非明示的なインスタンス化のAPI設計

クライアント独自の型で、クラステンプレートのインスタンス化を許可する場合は、 非明示的なインスタンス化を使用する必要があります。

例えば、クラステンプレートのStackを提供した場合は、クライアントが インスタンス化する型は事前には分かりません。

そこで、コンパイラはテンプレートの定義にアクセスする必要があるため、 ヘッダファイルにテンプレートの定義を記述する必要があります。 それは強固なAPIにとっては、大きなデメリットになります。

こうした状況では、実装の詳細は隠蔽出来なくても、隔離する努力を必要になります。

実装ヘッダへの格納

ヘッダファイルにテンプレート定義を格納する必要がある場合は、 クラス宣言部に直接インライン化するのが簡単なため、そうしたくなると思います。

しかし、これは強固なAPIにとっては悪い設計です。

これに代わる手段は、テンプレート実装の詳細を別ヘッダに格納する事です。

Stackクラスを例を使えば、メインのパプリックヘッダは以下になります。

// stack.h
#ifndef STACK_H
#define STACK_H

#include <vector>

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

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

// 全ての実装の詳細をヘッダに格納する
#include "stack_private.h"

#endif
// stack_private.h
#ifndef STACK_PRIVATE_H
#define STACK_PRIVATE_H

template <typename T>
void Stack<T>::Push(T value)
{
    // 実装
}

template <typename T>
T Stack<T>::Pop()
{
    // 実装
}

template <typename T>
bool Stack<T>::IsEmpty()
{
    // 実装
}

#endif

これは、Boostヘッダなど、多くの高品質なテンプレートベースのAPIで 使われているテクニックです。

メインのパプリックヘッダの実装の詳細を乱雑にせず、 必要な内部詳細の記述は、プライベートな詳細を格納する事が可能です。

ヘッダファイルにテンプレート定義を含めるテクニックは、 インクルードモデルと呼ばれています。

明示的なインスタンス化のAPI設計

事前に定義したテンプレート型のみの作成を提供し、 ユーザーに作成を許可したくない場合は、テンプレートの定義をヘッダに記述せず、 プライベートコードを完全に隠蔽する方法があります。

例えば、Vector3Dを作成した場合、このテンプレートは、 int、short、float、doubleのみの型を提供すれば問題ありません。 これ以上の型をユーザーに作成させる必要は無いと考えられます。

この場合は、明示的テンプレートのインスタンス化を使う事で、 .cppにテンプレート定義を格納する事が出来ます。

Stackの例を使って、明示的テンプレートのインスタンス化の例を示します。

template class Stack<int>;

これでコンパイラは、コード内のこの時点でint型のコードを生成するので、 その結果、コード内の別の場所で、明示的インスタンスを試みる事は無くなります。 明示的的なインスタンス化を行う事で、ビルド時間の短縮を図る事も出来ます。

では、この機能を活用するために、どのようなコードを組めばいいかを Stackクラスを例に見ていきます。

ヘッダファイルはほぼ同じです。 「#include "stack_private.h"」が無くなっているだけです。

// stack.h
#ifndef STACK_H
#define STACK_H

#include <vector>

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

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

#endif

これで、関連するcppファイルにテンプレートの定義を全て格納できます。

// stack_private.cpp
#include "stack.h"
#include <string>

template <typename T>
void Stack<T>::Push(T value)
{
    // 実装
}

template <typename T>
T Stack<T>::Pop()
{
    // 実装
}

template <typename T>
bool Stack<T>::IsEmpty()
{
    // 実装
}

// 明示的テンプレートのインスタンス化
template class Stack<int>;
template class Stack<double>;
template class Stack<std::string>;

ここで大事なのは最後の3行です。

template class Stack<int>;
template class Stack<double>;
template class Stack<std::string>;

この3行で、Stackクラステンプレートの明示的なインスタンス化を行っています。

実装の詳細は.cppファイルの格納されているため、ユーザーはこれ以上の 特殊化を行う事は出来ません。 これで実装の詳細は.cppファイルに完全に隠蔽された事になります。

ユーザーが使えるテンプレートの特殊化(ユーザーが使えるテンプレート引数)を 明示的にどれかを示すために、パプリックヘッダの最後にtypedefを追加します。

typedef Stack<int> IntStack;
typedef Stack<double> DoubleStack;
typedef Stack<std::string> StringStack;

C++ APIスタイル(フラットC API)

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

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

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

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を記述する場合には 多くの言語機能が使えなくなることに注意が必要です。

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 APIC++ APIのコードを比較したので、 今度は、それぞれのAPIスタイルを使うためのクライアントの記述を見ていきます。

まずは、C++ 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を正しくコンパイルとリンクが出来るようにしよう。

C++ APIスタイル(データ駆動型API)

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

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

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

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

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

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

  • データ駆動型API

データ駆動型API

データ駆動型APIとは・・・

データ駆動型プログラムとは、異なる入力データを供給する事で、 実行するごとに異なる操作を行うものです。

例えば、ディスク上に保存された命令実行リストを格納したファイル名を、 受け取るだけのプログラムがこの例です。

この手法がAPI設計にインパクトをもたらすのは、 様々なメソッド呼び出しを、提供するオブジェクトの集合に依存するのではなく、 名前付き命令や、名前付き、引数のディクショナリを受け取るもっと一般的な、 ルーチンを提供できるからです。

これは、メッセージ伝達APIまたは、イベントベースAPIと呼ばれます。 以下の関数の呼び出し例では、この種のAPIと標準なC/C++の呼び出しとの違いを示します。

  • func(obj, a, b, c): フラットなCスタイルの関数
  • obj.func(a, b, c): オブジェクト指向C++の関数
  • send("func", a, b, c): パラメータ付きのデータ駆動型関数
  • send("func", dict(arg1 = a, arg2 = b, arg3 = c):名前付き引数のディクショナ付きデータ駆動型関数(疑似コード)

具体的な例として、データ駆動型モデルでスタックの例を再設計してみます。

class Stack
{
public:
    Stack();
    Result Command(const std::string &command, const ArgList &args);
};

このシンプルなクラスは複数な操作を行うのに使用されます。

stack = new Stack();
if (stack)
{
    stack->command("Push", ArgList().Add("value", 10));
    stack->command("Push", ArgList().Add("value", 3));
    Stack::Result result = stack->command("IsEmpty");
    while (!result->convertToBool())
    {
        stack->command("Pop");
        result = stack->command("IsEmpty");
    }
    delete stack;
} 

これは文字列データで指定した、複数入力をサポートする 単一メソッドCommandで個々のメソッドを置き換えています。

各種コマンドを格納したテキストファイルの内容をパースし、 実行するプログラムを順に記述することは簡単です。

# データ駆動型スタックAPIの入力ファイル
Push value:10
Push value:3
Pop
Pop

データ駆動型スタックAPIを使って、このデータを受け取るプログラムは、 各行のスペースで区切られた最初の文字列を取るだけです。 (便宜上、#はコメントとして無視する) その後、コマンドに続くスペースで区切られた文字列に対して、 ArgListの構造体を作成します。 それから、Stack::Command()を実行し、ファイルの残りの処理を実行します。

このプログラムは異なるテキストファイルを共有する事で、 大幅に異なるスタック動作を実行できます。 しかも、プログラムを再コンパイルする必要もありません。

データ駆動型のWebサービス

APIによってコマンドがサーバに送られ、オプションとして結果をクライアントに返す クライアント/サーバ・アプリケーションをはじめとしたステートレスな、 コミニケーションチャネルには、データ駆動型が特に適しています。

また、疎結合コンポーネント間でメッセージを伝達する場合にも有効です。

中でも、Webサービスはデータ駆動型APIで表現するのに最適です。 Webサービスはクエリをパラメータの集合と共に、URLを送信したり、 JSONまたはXMLなど、構造化形式のメッセージを送ったりしてアクセスします。

例えば、ソーシャルサイトのDiggは、ユーザーがDigg.comのWebサービスと やり取りできるAPIをサポートしています。 具体例として、ある記事の詳細を読みたいユーザーのために、拡張情報を 返すAPIを提供しています。

これは以下の形式で、HTTPのGETリクエストを送信することで呼び出します。

http://service.digg.com/1.0/endpoint?method=digg.getinfo&digg_id=id

これは先程、提示したデータ駆動型APIにもうまく当てはまります。 先の例では、このようなHTTPリクエストを以下のように呼び出す事ができます。

digg = DiggWebServie();
digg->Request("digg.getinfo", ArgList().Add("digg_id", id)); 

データ駆動型APIのメリット

データ駆動型APIのメリットを以下にあげます。

プログラムのビジネスロジックを編集可能なデータファイルに抽象化 つまり、実行ファイルを再コンパイルせずに、プログラムの動作を修正できます。 しかも、ユーザーがこのデータファイルを簡単に記述できるように、 別のデザインツールをサポートする事もできます。

将来的なAPIの変更に対して十分に対応できる コマンドの追加や、削除、変更を行っても、多くの場合、 パブリックAPIメソッドのシグネイチャに影響がありません。

例えば、データ駆動型のスタックAPIを例にとってみましょう。 このAPIの新バージョンでは、Topコマンドを追加し、Pushコマンドを拡張し、 複数の値を受け取って、各値を順番にスタックにプッシュするようにしたとしましょう。

stack = new Stack();
stack->Command("Push", ArgList().Add("value", 10)..Add("value", 3));
Stack::Result result = stack->Command("Top");
int top = result.ToInt();

この新機能を追加しても、ヘッダファイルの関数シグネイチャには まったく何も変化がありません。 サポートした文字列と、Commandリストに渡す引数を変更しただけです。 この性質のため、データ駆動型APIでは、後方互換性を保ちながら、 変更を行う事がずっと簡単になります。

テストをサポートしやすい これは個別のテストプログラムやルーチンを、大量に記述して行う代わりに、 自動でAPIテストを行うテクニックです。

具体的には、実行する一連のコマンドや、チェックすべきアサートを格納した、 ファイルを読み込むデータ駆動型プログラムを用意するだけです。 新規テスト作成にコンパイルの必要がないため、テスト開発の繰り返しが 早くできるし、C++開発スキルがあまりないQAエンジニアでも、テストを記述できます。

IsEmpty => True // スタックは空
Push value:10
Push value:3
IsEmpty => False // 2つの要素を持つので空ではない
Pop => 3
IsEmpty => False // 1つの要素を持つので空ではない
Pop => 10
IsEmpty => True // スタックは空
Pop => NULL // 空のスタックをPopするとエラーになる

このテストプログラムは、データファイルからスタックコマンドを読み取った、 以前のプログラムに非常によく似ています。 主な違いは、[=>]サポートを追加して、Stack::Commandの返り値の、 結果をチェック出来るようにしたことです。 たったこれだけの事で、あなたのAPIに対して、無数のデータ駆動型テストを 作成できる柔軟性のある、テストフレームワークに出来上がっています。

データ駆動型APIのデメリット

データ駆動型APIのデメリットを以下にあげます。

実行時のコストがかかる このAPIのシンプルさと安定性には実行時のコストが付いてまわります。 これはコマンド名の文字列を呼び出すために、適切な内部ルーチンを検索する オーバーヘッドが余計にかかるためです。

ユーザーがパブリックヘッダを見ただけでは機能が分からない ユーザーがパブリックヘッダを見るだけでは、インターフェイスによって、 どんな機能とセマンティックが提供されているのかを、把握する事はできません。 とはいえ、サポートされているコマンドと適切な引数のリストを明示した、 優れたAPIドキュメントを用意すれば、このデメリットを十分に補う事ができます。

インターフェイスコンパイル時チェックをしてもメリットがない パラメータの解析と、型チェックはAPI開発で行う事になります。 そこでコードをテストし、重要な動作を壊さないことを確認するのは、 開発者の型にかかるため、開発者の責任は大きくなります。

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使用するクライアントにも降り掛かります。

ラッピングパターン(プロキシ、アダプター、ファサード)

今、「C++のためのAPIデザイン」を読んでます。 ラッピングで使用するデザインパターンについてまとめてみました。

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

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

ラッピングについて・・・

APIを設計する上で、クラスの集合の上に被せるようなラッパーインターフェイスを 記述することはよくあります。

例えば・・・

  • 大量のレガシーコードをベースに使う場合に、ベースのアーキテクチャを変更するのではなく、レガシーコードを隠蔽するようなクリーンなAPIを新たに設計する場合
  • C++APIを既に記述してあり、プレーンなCのAPIを開示する必要が出た場合

ラッパーのデメリットは、関節性のレイヤーが増えることで余分な状態を 保存することになりパフォーマンスに影響が出ることがあります。 とはいえ、先ほど述べた例に対して高品質で焦点を絞ったAPI設計が出来れば コストは十分に元は取れると思います。

ラッピングに使用するデザインパターン

あるインターフェイスを別のインターフェイスでラップする処理は、 構造に関するデザインパターンを使う事が出来ます。

上記は、ラッパーレイヤーと元のインターフェイス間の距離順に並んでいます。

プロキシパターン

プロキシパターンは、別のクラスへの1対1の転送インターフェイスを提供します。 つまり、プロキシクラスのFunctionA()を呼び出すと元のクラスのFunctionB()が 呼び出されます。 すなわち、プロキシクラスと元のクラスのインターフェイスは同じになります。

以下に例を示します。

class Proxy
{
public:
  // コンストラクタ
  Proxy() : original_(new Original())
  {
  }
  // デストラクタ
  ~Proxy()
  {
    delete original_;
  }
  bool DoSomething()
  {
    return original_->DoSomething();
  }

private:
  Original original_;
};

通常、プロキシパターンはプロキシクラスに元のクラスのポインタを 保存させて実装します。 プロキシクラスのメソッドは元のオブジェクトの同名メソッドにリダイレクトするだけです。

このテクニックのデメリットは、元のオブジェクトの関数を再提示、 コードの重複と基本的に同じプロセスする必要があることです。 そこで、この手法を使う場合は元のオブジェクトを変更する時には プロキシインターフェイスの整合性を維持するように継続的に努力する必要があります。

この手法を更に強化する方法として、プロキシAPIと元のAPIに共有させる 抽象インターフェイスを使う方法があります。 これは双方のAPIの同期をさせ続ける良い方法ですが、元のAPIが修正出来る必要があります。

// 抽象インターフェイス
class IOriginal
{
public:
  virtual bool DoSomething() = 0;
};

// 元のAPIクラス
class Original: public IOriginal
{
public:
  bool DoSomething();
};

// プロキシクラス
class Proxy : public IOriginal
{
public:
  // コンストラクタ
  Proxy() : original_(new Original())
  {
  }
  // デストラクタ
  ~Proxy()
  {
    delete original_;
  }
  bool DoSomething()
  {
    return original_->DoSomething();
  }

private:
  Original original_;
};

プロキシパターンのユースケース

  • 元のオブジェクトのレイジーインスタンス:このケースでは元のオブジェクトはコンストラクタでインスタンス化されますが、インスタンス化が高負荷の場合は引き伸ばしが可能です。
  • 元のオブジェクトへのアクセス制御:ProxyとOriginalの間に許可レイヤーを挿入し、Originalの特定メソッドしか呼び出せないようにします。
  • Originalクラスをスレッドセーフにミューテックスロックを追加します。最も効率の良い方法とは言えませんが、Originalを変更できない場合に有用です。
  • リソース共有のサポート:同じOriginalクラスを複数のProxyクラスで共有します。参照カウンタの実装や、データを共有しメモリ使用容量を最低限にします。
  • 将来的な変更に対してOriginalクラスを保護する:将来、Originalクラスに変更があった場合にプロキシクラスを通じてインターフェイスを保持することが出来ます。ただし、OriginalとProxyのインターフェイスに差異が出たため、プロキシパターンではなくなります。

アダプターパターン

アダプターパターンとは、あるクラスのインターフェイスを互換性のある 異なるインターフェイスに変換するものです。 プロキシと似ていますが、アダプターは元のクラスのインターフェイスとは異なります。

アダプターを使うと、ほかのコードで使うために既存のAPIの別のインターフェイスを 開示することが出来ます。

ここでは矩形の表示情報を定義するクラスを例に考えてみます。

class RectAdapter
{
public:
  // コンストラクタ
  RectAdapter() : rect_(new Rect())
  {
  }
  // デストラクタ
  ~RectAdapter()
  {
    delete rect_;
  }
  void Set(float x1, float y1, float x2, float y2)
  {
    // Rectクラスに合わせて中心座標とサイズに変更
    float w = x2 - x1;
    float h = y2 - y1;
    float cx = w / 2.f + x1;
    float cy = h / 2.f + y1;
    rect_->setDimensions(cx, cy, w, h);
  }

private:
  Rect rect_;
};

メソッドパラメータの順序が自分のAPIとは異なったり、座標システムが異なったり、 約束事が異なったり(中心とサイズ、左上、左下など)、メソッド命名法が 自分のAPIと異なったりする場合があります。 そこで、アダプタークラスを使ってインターフェイスAPIと互換性がある形に 変換します。

この例では、RectAdapterとRectでは異なるメソッド名や呼び出し規則を使って 矩形の設定をしています。 機能性はどちらも同じですが、別のインターフェイスを開示して簡単に処理できるようにします。

なお、アダプターは上の例を継承でも実装できます。 両者それぞれは、オブジェクトアダプターとクラスアダプターと呼ばれます。 パプリック継承も可能ですが、プライベート継承を使って新しいインターフェイスのみ 公開するほうがよいと思われます。

アダプターパターンの利点

  • APIの一貫性を強化:アダプターパターンを使うと、異なるインターフェイス様式を持つ異質のクラスをきちんとそろえ、全てに対して一貫したインターフェイスを提供できます。
  • APIの依存ライブラリをラップ:例えば、PNG画像をロードする機能を持つAPIの場合、libpngを使って実装したいがユーザーにはlibpngの存在を隠蔽し、将来的にlibpngの変更に備えて保護します。
  • データ型を変換:上記の例で示したように、異なるシステムや約束事を持つ2つのデータ型を内部で変換することが出来ます。
  • API用に異なる呼び出し規則を開示:例えば、プレーンなCのAPIを記述し、C++ユーザー向けにオブジェクト指向バージョンを提供したい場合に、Cの呼び出しをC++クラスにラップすることが出来ます。

ファサードパターン

ファサードは、他のクラスの集合に対してシンプルにしたインタフェースを提供します。 実際には、クラス群を使いやすくするためにそれらをまとめたハイレベルな インターフェイスを定義したいものです。 ファサードがクラスの構造を簡素化するのに対して、アダプターはクラスの構造を そのまま保持する点で異なっています。

では例を示します。

あなたは休暇でホテルに泊まっています。 夕食を食べてからショーを見に行くことにしました。 そのためには以下をする必要があります。 ・夕食のレストランの予約 ・劇場の席の予約 ・ホテルからのタクシーの予約 これら3つのやりとりをC++で表現してみます。

// タクシークラス
class Taxi
{
public:
  bool BookTaxi(int people, time_t time);
};
// レストランクラス
class Restaurant
{
public:
  bool ReserveTable(int people, time_t time);
};
// 劇場クラス
class Theater
{
public:
  time_t GetShowTime();
  bool ReserveSeats(int people);
};

さて、泊まっているホテルは高級で、優秀なコンシェルジュがこの全てを アレンジしてくれることにしましょう。 ショーの時間をつきとめ、適切な夕食の時間を割り出し、タクシーも手配してくれます。 これをC++設計で使う用語に変換すると、ずっとシンプルなインターフェイスを持つ 一つのオブジェクトとやりとりするだけで済むはずです。

// コンシェルジュクラス
class ConciergeFacade
{
public:
  time_t BookShow(int people);
};

ファサードパターンの応用

  • レガシーコードの隠蔽:壊れやすく一貫性のない朽ち果てたレガシーコードを相手にする場合は少なくありません。こうした場合、古いコード上に新たに分かりやすい設計のAPIをを作成したほうがずっと簡単になります。こうすれば新しいコードは全て新しいAPIを使うことになり、レガシーコードは完全に隠蔽出来ます。
  • コンビニエンスAPIの作成:一般的に柔軟性のあるルーチンにするか、共通ユースケースを簡単に処理できるシンプルで使いやすいルーチンにするか、決められない場合があります。ファサードなら両方のルーチンを共存させることが出来ます。(例として、OpenGLライブラリではローレベルの基本ルーチン(GLライブラリ)の提供と、基本ルーチン上に構築したハイレベルで使いやすいルーチン(GLUライブラリ)を提供している)
  • 機能のスワップAPIへのアクセスを抽象化することで、クライアントコードに影響を与えず特定の機能へ置き換えが可能です。これはデモやAPIのテストバージョンのサポートに使用できます。さらに、とあるゲーム用に別のレンダリングエンジンを使ったり、別の画像読み込みライブラリを使ったりするなど異なる機能へのスワップも可能になります。