2011-06-02-01-いまさら後悔していること - project-enigma

2011-06-02-01-いまさら後悔していること

>> Site top >> weblog >> 月別アーカイブ >> 2011年06月のlog >> 2011-06-02-01-いまさら後悔していること

最終更新日付:2014/01/02 00:00:00


いまさら後悔していること

2011 年 06 月 02 日

VBGeneric では、メソッドを跨いで渡された反復子を移動させる場合、コピーを明示的に作成せねばならない。その理由は、Visual Basic のオブジェクト渡しの方式にある。オブジェクトの引数渡しは全て参照渡しになるため、オブジェクトの変更は呼び出し元に波及する。そして、反復子もオブジェクトだから、その移動が呼び出し元に波及しないようにするには、どこかでオブジェクトのコピーを作る必要がある。今回はそこにある「無駄」の話。

反復子のコピーを作らないでアルゴリズムを実行するとどうなるだろうか。仮に Algorithm.ForEach がそうなっていたとして、以下のコードで何が起きるだろう。

   Dim gv   As GVector
   Dim itr1 As InputIterator
   Dim itr2 As InputIterator
     :
     :
   Set itr1 = gv.First()
   Set itr2 = gv.Last()

   Call Algorithm.ForEach(itr1, itr2, New FooFunc)
   Call Algorithm.ForEach(itr1, itr2, New BarFunc)

答えは、FooFunc は gv.Size() に等しい回数コールされるが、BarFunc は1回もコールされない、だ。これはコードをぱっと見た際の直感に反する(少なくとも大方の人にとってはそうだろう)。だからアルゴリズム中で移動される反復子にはコピーが必要なのだ。

そして、反復子のコピーを作成するのは(一般に)アルゴリズム側の仕事だとした。これはかれこれ8年くらい前の決定で、VBGeneric のアルゴリズムはそれを前提に実装されている。その理由は、呼び出し側が煩雑になるから‥‥‥だった。タイトルの「いまさら後悔していること」というのはこれだ。

なぜ後悔しているかというと、このルールでは無駄なコピーが発生しうるからだ。たいていの場合、コンテナのシーケンスをアルゴリズムに渡す場合、以下のような記述をするはずだ。

   Dim gv As GVector
     :
     :
   Call Algorithm.ForEach(gv.First(), gv.Last(), New FooFunc)

ここで起きていることを確認しよう。First / Last の両メソッドがアロケートした反復子オブジェクトは Algorithm.ForEach に渡される。そして、ForEach はルールに従って反復子のコピーを作成する。ForEach が移動するのは最初のパラメータだけだから、作成するコピーは1つだ。

しかし、この場合に限って言えば、ここで ForEach がコピーを作成する必要はなかった。なぜなら、呼び出し元が用意した反復子オブジェクトは一時オブジェクトだからだ。もちろん、ForEach は呼び出し元のそんな都合など知らないから、ルールに従ってコピーを作成する。結果的にそれが無駄だというだけの話だ。

問題は、アルゴリズム内での(反復子の)コピー作成の大半が「無駄」に該当するのではないか、ということだ。複雑なことをやる場合は別としても、自分で書くコードの大半は、前述のように First / Last メソッドが作成するオブジェクトをそのまま渡している。これがコードの8〜9割を占めているとすれば、「コピー作成の責任は呼び出し元にある」というルールでアルゴリズムを構成した方が効率が良かっただろう。

しかし、VBGeneric は最初のリリースからすでに8年が経過している。今ルールを変更して、アルゴリズムの実装をそれに合わせたら、既存コードに影響が出る。大した影響ではないのかもしれないが、完全なゼロでなければおいそれとはできない。

ところで、仮にルールを変更したら最初のコードはどうなるだろうか。おそらくは、以下のように書くことになる。

   Call Algorithm.ForEach(itr1.Clone(), itr2, New FooFunc)
   Call Algorithm.ForEach(itr1.Clone(), itr2, New BarFunc)

これは、ForEach が最初の反復子パラメータを移動するからだが、ForEach の動作について良く知らない人は以下のように書くかもしれない。

   Call Algorithm.ForEach(itr1.Clone(), itr2.Clone(), New FooFunc)
   Call Algorithm.ForEach(itr1.Clone(), itr2.Clone(), New BarFunc)

これはこれでやはり無駄だ。自分も、普段使用しないアルゴリズムだったら同じようにしてしまうかもしれない。そして、これはルールを変更する前と同じくらいの無駄になるかもしれない。

こう考えてみると、なんだかんだと言っても現在のルールが良いのかもしれない。そしてひょっとしたら、8年前の自分も同じように考えて現在のルールに決めたのかもしれない。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : STLとその移植

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.