Subscribed unsubscribe Subscribe Subscribe

STL のイテレータは「やりすぎ」か

最近大学生にC++を教える機会がありました。第一印象は (失礼な物いいですが) とてもプログラミングなどとは無縁な生活を送っているかのような学生だったので、なぜC++を勉強しているのかと聞いたら、先学期に授業でPerlを勉強してMarkov Cluster Algorithmを実装したところ、非常に処理速度が遅かったので、先輩に「C++で実装するといいよ」と言われたからとのこと。

Perlから勉強するとどうも「型」というものに馴染みがないからか、char[] と char * と std::string の違いだとか、クラスや構造体がなぜ必要かということとかを説明するのに一苦労でした。とくに C++ の文字列リテラルにまつわるシンタクティック・シュガーが混乱を招いている印象を受けました。

初めの一歩として Perl のコードを map や vector を使って置き換えるやりかたを提案し、いろいろサンプルコードを書いてはそれを見せながら説明するということを繰り返していたのですが、どうしても説明に窮す瞬間がありました。それが STLイテレータです。

typedef std::vector<std::string> list_type;
list_type list;

for (list_type::iterator i = list.begin(); i != list.end(); ++i) {
    std::cout << "名前: " << *i << "\n";
}

このようなプログラムなのですが、演算子のオーバロードを説明していない時点では、なんでintの変数じゃないのに「++」を使うの?とか、 std::string を取り出すのになぜ「*」を使うの?などと矢継ぎ早に質問攻めに遭ってしまいました。「説明する順序が間違ってるんだよバカ」と言う意見もあるかもしれませんが、とにかく個人的にはイテレータのセマンティクスに対して、演算子を割り当てるのには強引かつやりすぎじゃないかという気は前からしていました。慣れれば短く書くことができるので便利だという側面もありますけど。

私がライブラリを設計するときは、かならず明示的な名前のメソッドと、演算子のオーバロードによるそれらの操作の抽象化の両方を提供するよう心がけています*1。もともと Java 屋だったからかもしれませんが、

typedef std::vector<std::string> list_type;
list_type list;

for (list_type::iterator i = list.begin(); i != list.end(); i.next()) {
    std::cout << "名前: " << i.current() << "\n";
}

のように明示的な記述のコードの方が、すっと頭に入ってくるんですよね。しかし、抽象代数のような枠組でオブジェクトの振舞いを見ると、概念が非常に整理されることが多いのも事実。両者のいいところをうまく表現できて、しかも他者にとって読解しやすいソースコードに仕上げるのが常に課題です。

*1:記述法が複数になって好ましくないという意見もあると思いますが、そこはコーディング規約の問題と考えてください