[迷信] アルゴリズム関数内で関数オブジェクトはコピーされない
まずは次のコードをご覧ください。
#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 関数では、アルゴリズム関数 generate に countup オブジェクトを適用して、配列 array の各要素の値を生成しています。
(エラーチェックがないことを除けば)一見何の問題もなさそうなコードですが、実は、深刻な移植性の問題を抱えています。
このコードでは、countup クラスの一時オブジェクトを generate 関数の実引数として渡したときに一度コピーが発生することを除けば、generate 関数内ではそれ以上コピーが発生しないことを前提としています。しかし、標準規格ではそのようなことは保証されていません。例えば、generate 関数が、
void generate(ForwardIterator first, ForwardIterator last, Generator gen)
{
while (first != last)
{
Generator g(gen);
*first++ = g();
}
}
といった実装であったとしても、規格上は特に問題がありません。実際には、効率を考えるとこんなことをする理由はないのですが、もしかすると、処理系定義の何らかの方法で、関数オブジェクトをループの一周ごとにチェックできるようなケースもないとはいいいきれません。
こうした問題を解消するには、アルゴリズム関数内で関数オブジェクトは唯一つのインスタンスが用いられるという前提を捨てなければなりません。具体的には、
#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 クラスの内部で参照カウンタ方式のスマートポインタを用いるなど、非常にオーバーヘッドの大きな方法を選択せざるを得なくなります。
トラックバック
ブックナビゲーション
- 技術情報
- Boost C++ Libraries メモ
- C++と組込み環境
- C++サンプル集
- C++テンプレート集
- C++プログラマのためのC言語入門
- C/C++迷信集
- [迷信] 'A'~'Z' の値は連続している
- [迷信] 0xe-0xe はゼロ
- [迷信] 1 バイトは 8 ビット
- [迷信] 2の累乗による割り算と右シフトは等価
- [迷信] FILE 型は構造体
- [迷信] abs は常に非負の値を返す
- [迷信] argv[0] はプログラム名
- [迷信] char 型は符号付き
- [迷信] double の出力書式は "%lf"
- [迷信] fflush で入力バッファをクリア
- [迷信] free でメモリを開放する
- [迷信] free に NULL を渡すとクラッシュする
- [迷信] gets は単純に fgets に置き換えられる
- [迷信] isalpha 関数の引数は char 型
- [迷信] new に失敗すると NULL が返る。
- [迷信] scanf ではバッファオーバーランを防げない
- [迷信] scanf でキーボードから入力
- [迷信] setjmp マクロの返却値は変数に代入できる
- [迷信] sizeof は定数式
- [迷信] void main(void)
- [迷信] とりあえず memset で初期化
- [迷信] アルゴリズム関数内で関数オブジェクトはコピーされない
- [迷信] オブジェクトの動的生成に失敗するとメモリリークする
- [迷信] コンストラクタから例外を送出してはならない
- [迷信] コンストラクタで自身をゼロクリア
- [迷信] コンパイラはプログラマの心を察してくれる
- [迷信] コンパイルエラーが出るのでアクセス指定子を修正
- [迷信] ソースコード中の即値を全廃せよ
- [迷信] ソースファイルの末尾に }
- [迷信] データ列のソートには qsort 関数を使うべし
- [迷信] プログラムは必ず main から始まる
- [迷信] 一重引用符の中には一文字しか書けない
- [迷信] 今どき int が 16 ビットの処理系なんて無い
- [迷信] 入力データ格納用配列のサイズは BUFSIZ
- [迷信] 割付けたメモリはプログラマが自分で解放しなければならない
- [迷信] 実数型とは浮動小数点型のことである
- [迷信] 引用符で囲んだヘッダ名はカレントディレクトリから探索する
- [迷信] 文字列から整数への変換には atoi
- [迷信] 構造体のタグ名は下線で始める
- [迷信] 構造体はクラスではない
- [迷信] 識別子に使える文字は英数字と下線のみ
- [迷信] 非局所オブジェクトは外部結合
- C言語再入門
- C言語徹底入門
- Drupal メモ
- TOPPERS 情報
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 車輪の再発明
- 過去の情報


少し改良してみました
このような方法ではだめなのでしょうか?
#include <iostream> #include <algorithm> #include <iterator> class countup { public: explicit countup(int init = 0) : value_(init),value_p(&value_) { } countup(countup &src) : value_(0),value_p(&(src.value_)) { } countup(const countup &src) : value_(src.value_),value_p(&value_) { } countup& operator=(countup &src) { value_=0; value_p=&(src.value_); return *this; } countup& operator=(const countup& src) { value_=src.value_; value_p=value_; } countup int operator()() { return (*value_p)++; } private: int value_; int *value_p; }; namespace my{ template<class ForwardIterator, class Generator> void generate(ForwardIterator first, ForwardIterator last, Generator gen) { while (first != last) { Generator g(gen); *first++ = g(); } } } int main() { int array[10]; my::generate(array + 0, array + 10, countup()); std::copy(array + 0, array + 10, std::ostream_iterator<int>(std::cout, "n")); return 0; }Re:少し改良してみました
だめではないですが、ちょっと分かりにくいですね。
これは、アルゴリズム関数呼び出しの際に渡した実引数の生存期間が、アルゴリズム関数から戻るまでの間持続することに依存しています。それで間違いありませんが、直感的ではないと思います。
ただ、オーバーヘッドは少ないので、効率の点ではいいですね。問題が生じるケースがないか、もう少し考えてみる必要はありそうですが...。
やはりダメそうです。
const参照を使ったコピーだと、カウント値がコピーされることになるので、解決にならないようです。
_
うーん、考え方そのものはまずくないはずなのですが…
const&によるコピーの可能性を捨て切れなかったので、仕方ないのでconst&のコピーコンストラクタとoperator=を用意する必要がありました。