[ C++で開発 ]

STLプログラミング・ノート

STLを使いこなすためのノートです。STLは機能豊富で汎用的なライブラリ集ですが、使いこなすには難易度が少々高いので、あらかじめ勉強が欠かせません

コンテナの要素にポインタを入れたい

ポリモーフィズムを活用するためのコンテナ格納法

オブジェクト指向プログラマーがぶつかるSTLの壁の1つです。

オブジェクト指向プログラミングをする以上、アプリケーションプログラムを書く際にはクラスをいくつも定義します。ポリモーフィズムを活用するため継承を使います。しかし、STLのコンテナにオブジェクトを入れようとして、はたと悩みます。世の中のSTLのサンプルは、コンテナに値をコピーで格納します。コピーだと、ポリモーフィズムが使えません。困った・・・

ポインタをコンテナの要素に

そこで、苦肉の策としてコンテナにポインタを格納することにします。

stl_pointer.cpp
#include <list>

class Person { ... };
class Employee : public Person { ... };

void testPointer() {
    std::list<Person*> persons;
    persons.push_back(new Person("Thomas"));
    persons.push_back(new Person("Percy"));
    persons.push_back(new Employee("Henry"));

    for_each(persons.begin(), persons.end(), std::mem_fun(&Person::print));
}

オブジェクト指向プログラミングでは、クラスのメンバー関数にそのクラスのオブジェクトに対する操作を定義するので、STLを使用する場合、アルゴリズムの関数オブジェクトとしてクラスのメンバー関数を指定したくなります。

ポインタを要素にしたコンテナの場合、各要素はポインタなので、std::mem_funでメンバー関数のアドレスを指定します。

これを実行すると、以下の実行イメージになります。

Person{name=Thomas} Person{name=Percy} Employee{name=Henry}

さて、これで終われば簡単なのですが、上記コードはメモリ管理の問題があります。関数testPointerのスコープを抜けると、ローカル変数として定義したpersonsは破棄されますが、その要素の各ポインタが指すメモリは破棄されません。

clearだけでは各要素のメモリは解放されない
    ...
    persons.clear();
}

std::listには、全要素を除去するclear()がありますが、残念ながらポインタをdeleteはしてくれません。

要素のポインタをすべてdeleteする(forループで)
    ...
    for (std::list<Person*>::iterator it = persons.begin(); it != persons.end(); ++it) {
        delete *it;
    }
    persons.clear();
}

for文でiteratorを操作してもいいのですが、ここではSTLアルゴリズムっぽくfor_eachを使ってみます。

要素のポインタをすべてdeleteする(for_eachで)
struct DeleteObject {
    template <typename T>
    void operator()(const T* ptr) const {
        delete ptr;
    }
};

    ...
    for_each(persons.begin(), persons.end(), DeleteObject());
    persons.clear();
}

スマートポインタをコンテナの要素に

ポインタはメモリ管理が難しいので、スマートポインタを使う方法が適しているでしょう。

stl_pointer.cpp
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/mem_fn.hpp>

class Person { ... };
class Employee : public Person { ... };

typedef boost::shared_ptr<Person> PersonPtr;

void testSmartPointer() {
    std::list<PersonPtr> persons;
    persons.push_back(PersonPtr(new Person("Thomas")));
    persons.push_back(PersonPtr(new Person("Percy")));
    persons.push_back(PersonPtr(new Employee("Henry")));

    for_each(persons.begin(), persons.end(), boost::mem_fn(&Person::print));
}

関数testSmartPointerのスコープを抜けてstd::listが破棄されるときに、各要素のスマートポインタで管理されるポインタがdeleteされます。

スマートポインタには、std::mem_funとstd::mem_fun_refは適用できないため、boost::mem_fnを使用しています。

配列

配列をSTLコンテナのようにアルゴリズムから扱えると便利です。