[迷信] アルゴリズム関数内で関数オブジェクトはコピーされない

まずは次のコードをご覧ください。

#include <iostream>
#include <algorithm>
#include <iterator>
 
class countup
{
public:
  explicit countup(int init = 0)
    : value_(init)
  {
  }
  int operator()()
  {
    return value_++;
  }
private:
  int value_;
};
 
int main()
{
  int array[10];
  std::generate(array + 0, array + 10, countup());
  std::copy(array + 0, array + 10, std::ostream_iterator<int>(std::cout, "\n"));
  return 0;
}

簡単に上のコードの説明をします。countup クラスは、コンストラクタで与えられた値を初期値として、関数呼出し演算子を用いて呼出されるごとにインクリメントした値を返します。main 関数では、アルゴリズム関数 generatecountup オブジェクトを適用して、配列 array の各要素の値を生成しています。

(エラーチェックがないことを除けば)一見何の問題もなさそうなコードですが、実は、深刻な移植性の問題を抱えています。

このコードでは、countup クラスの一時オブジェクトを generate 関数の実引数として渡したときに一度コピーが発生することを除けば、generate 関数内ではそれ以上コピーが発生しないことを前提としています。しかし、標準規格ではそのようなことは保証されていません。例えば、generate 関数が、

template<class ForwardIterator, class Generator>
void generate(ForwardIterator first, ForwardIterator last, Generator gen)
{
  while (first != last)
  {
    Generator g(gen);
    *first++ = g();
  }
}

といった実装であったとしても、規格上は特に問題がありません。実際には、効率を考えるとこんなことをする理由はないのですが、もしかすると、処理系定義の何らかの方法で、関数オブジェクトをループの一周ごとにチェックできるようなケースもないとはいいいきれません。

こうした問題を解消するには、アルゴリズム関数内で関数オブジェクトは唯一つのインスタンスが用いられるという前提を捨てなければなりません。具体的には、

#include <iostream>
#include <algorithm>
#include <iterator>

class countup
{
public:
  explicit countup(int& init)
    : ptr_(&init)
  {
  }
  int operator()()
  {
    return (*ptr_)++;
  }
private:
  int* ptr_;
};

int main()
{
  int array[10];
  int x = 0;
  std::generate(array + 0, array + 10, countup(x));
  std::copy(array + 0, array + 10, std::ostream_iterator<int>(std::cout, "\n"));
  return 0;
}

といった対策が必要になってきます。あるいは、元の使い勝手と同じにするには、coutup クラスの内部で参照カウンタ方式のスマートポインタを用いるなど、非常にオーバーヘッドの大きな方法を選択せざるを得なくなります。

この記事のトラックバックURL:

http://www.kijineko.co.jp/trackback/383