smart pointerを使う場面

なんか話題になっていたので、具体例を考えてみたい。

継承を使っているとき

通常、C++で継承は使わない。テンプレートの方がたいてい実行時性能がよいので、テンプレートを使うからである[要出典][独自研究]。

ライブラリの場合は普通ユーザーがコードを書くので、テンプレートをガンガン使える。だが作っているものがアプリケーションで、ユーザーはC++コードを書かない(GUICUI、他言語へのバインディング、入力ファイルを使う、など)場合、テンプレートを使って多様な機能を持たせることはできない(内部で使うことはできるが、ユーザーに指定させることはできない)。どんなオブジェクトを作る必要があるかが実行してはじめてわかるからで、実行中にテンプレートを実体化してコンパイルし直すわけにはいかないからだ。テンプレートを使うなら、先に必要になりそうなものを実体化しておき、必要に応じて取り替えられるようにしないといけない。

そのような場合、共通のインターフェースを持つクラスを仮想基底クラスから必要になりそうなクラスを派生させ、基底クラスへのスマートポインタを持ちまわる、という解決策がある。例えば、何らかの最適化問題を解くプログラムを書いており、どのアルゴリズムを使うかを入力ファイルから与えるなら、以下のようなインターフェースにまとめることができるだろう。

class SolverBase
{
    virtual void solve() = 0;
};

class GradientDescentSolver: public SolverBase
{
    void solve() {/* ... */}
    // 他のメンバがたくさんある
};

class ConjugateGradientSolver: public SolverBase
{
    void solve() {/* ... */}
    // 他のメンバがたくさんある
};

// ...

入力ファイルを読んでパラメータを設定した派生クラスを作り、それを指す基底クラスへのポインタを返す。このときに、スマートポインタを使う。

std::unique_ptr<SolverBase> read_input(const std::string& filename)
{
    const auto filedata = parse_file(filename);
    if(algorithm == "gradient descent")
    {
        const double delta = read<double>(filedata, "delta");
        const double tolerance = read<double>(filedata, "tolerance");
        const std::ofstream output(read<std::string>(filedata, "output"));
        return std::make_unique<GradientDescentSolver>(delta, tolerance, output);
    }
    else if(...)
    {
        // ...
    }
}

mainの中ではSolverBaseを受け取ればいいので、実行時にどんな型が指定されていても同じmainが動く。

int main(int argc, char **argv)
{
    if(argc != 2)
    {
        std::cerr << "usage: ./solver [input file]\n";
        return 1;
    }
    const std::string filename(argv[1]);
    const std::unique_ptr<SolverBase> solver = read_input(filename);
    solver->solve();
    return 0;
}

もちろんmainでなくて、コンテナに格納したりすることもできるようになる。

現実の問題でここまで単純になることはあまりないだろうけれど、大体こんな感じだ。

ポインタを返すCライブラリを使っているとき

例えば、ゲーム製作などに使われているSDLというライブラリがあるが、これでwindowを立ち上げることを考えてみる。

Simple DirectMedia Layer - Homepage

このライブラリは、ウィンドウを表示するために、以下のような関数でウィンドウオブジェクトを作り、使い終わったら開放する必要がある。

SDL_Window* SDL_CreateWindow(const char* title,
                             int         x,
                             int         y,
                             int         w,
                             int         h,
                             Uint32      flags);

void SDL_DestroyWindow(SDL_Window* window);

これはそもそもポインタを使う以外に選択肢がない。となれば、生ポインタを使うよりは、スマートポインタで解放を自動化しよう。

std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)>
   window(SDL_CreateWindow(...), &SDL_DestroyWindow);

もはやリソースリークに怯える必要はない。

noncopyable かつ immovable なクラスをどうしてもmoveしたいとき

std::mutexがそれにあたる。これはメモリ上の特定の位置にあり続けないといけない。

このようなクラスを持ちまわる時、実体はメモリ上の1点に固定しておいて、std::unique_ptr<std::mutex>を代わりに持って回るということはあり得る。例えば、std::vectorは要素がcopyableか、少なくともmovableであることを要求する(サイズが増減した時は別のメモリ領域に動かすことがあるため)。そのような場合、std::mutexを入れることはできない。あるいは、もしかしたらこれがstd::unique_ptr<T[]>の使いどきなのかもしれない。しらんけど。