2

NM Jousttis の「The C++ Standard Library」セクション 5.9 からの引用


#include < iostream>
#include < list>
#include < algorithm>

using namespace std;

//function object that adds the value with which it is initialized
class AddValue {
    private:
       int the Value; //the value to add
    public:
       //constructor initializes the value to add
       AddValue(int v) : theValue(v) {    }
       //the "function call" for the element adds the value
       void operator() (int& elem) const {  elem += theValue; }
 };

int main()
{
      list<int> coll;
      for (int i=1; i<=9; ++i) 
         coll.push_back(i); 

      //The first call of for_each() adds 10 to each value:
      for_each (coll.begin(), coll.end(), AddValue(10)) ; 

ここで、式 AddValue(10) は、値 10 で初期化されるタイプ AddValue のオブジェクトを作成します。AddValue のコンストラクターは、この値をメンバー theValue として格納します。for_each() 内では、coll の各要素に対して "()" が呼び出されます。繰り返しますが、これは、渡された AddValue 型の一時関数オブジェクトに対する operator () の呼び出しです。実際の要素が引数として渡されます。関数オブジェクトは、その値 10 を各要素に追加します。要素は次の値を持ちます: 10 を追加した後:

11 12 13 14 15 16 17 18 19

for_each() の 2 回目の呼び出しでは、同じ機能を使用して、最初の要素の値を各要素に追加します。コレクションの最初の要素で AddValue 型の一時関数オブジェクトを初期化します。

for_each (coll.begin(), coll.end(), AddValue (*coll. begin()) ) ; 

最初の要素を追加した後の出力は次のようになります。

22 23 24 25 26 27 28 29 30

私が理解していないのは、出力がそうでない理由である2番目のケースです

22 34 35 36 37 38 39 40 41

呼び出しごとに新しいファンクターが作成されることを意味しますか、それとも呼び出しごとにファンクターが使用されますか?

4

6 に答える 6

6

この式AddValue(*coll. begin())は、 class の 1 つの一時オブジェクトを作成しますAddValue。その一時的なものは関数に渡されfor_eachます。for_each次に、オブジェクトの関数呼び出し演算子を呼び出します - つまり、 - からまでoperator()の各要素に対して 1 回。coll.begin()coll.end()

技術的にfor_eachは、functor パラメーターを (参照ではなく) 値で受け取るため、実際には一時自体ではなく一時のコピーで動作します。

于 2010-06-26T03:06:56.810 に答える
1

[編集] 私は元々、著者の当初の意図を誤解していました。間違いを訂正しました。

for_each (coll.begin(), coll.end(), AddValue (*coll.begin()) ) ;

最初の要素を追加した後の出力は次のようになります。

22 23 24 25 26 27 28 29 30

私が理解していないのは、出力がそうでない理由である2番目のケースです

22 34 35 36 37 38 39 40 41

呼び出しごとに新しいファンクターが作成されることを意味しますか、それとも呼び出しごとにファンクターが使用されますか?

まず第一に、 for_each アルゴリズムが渡したファンクターをコピーするかどうかは関係ありません。関連するのは、イテレータまたはポインタを指す先ではなく最初の要素に格納する必要があるということです。それを行い、毎回逆参照すると、うまくいくはずです。

struct AddValue 
{
    const int* ptr;
    explicit AddValue(const int* iptr): ptr(iptr) {}
    void operator() (int& elem) const {elem += *ptr; }
};

int main()
{
    vector<int> v;
    for (int j=1; j <= 9; ++j)
        v.push_back(j + 10);
    for_each(v.begin(), v.end(), AddValue(&v[0]) );
    // v will be [22 34 35 36 37 38 39 40 41]
}

ただし、関数型プログラミングについては 2 セントを投入する必要があります。関数型プログラミングを使用すると、非常に簡潔で巧妙にタイトなコードを作成できます。ただし、デバッグも非常に面倒であり、コードを分散化する効果もあります。あなたのコードがそれから大きな利益を得られない限り、可能な場合はシンプルな反復子ベースの for ループを検討してください。これにより、仲間のプログラマーがコードをデバッグしたり読んだりする時間がはるかに簡単になります。

私は過去に関数型プログラミングを非常に手荒く扱ってしまうという過ちを犯し、チームから嫌われていました。私は、組み合わせ述語ロジックを使用して悪魔のようにタイトなコードを書くことに夢中になり、何度も何度も組み合わせて再利用できる関数オブジェクトの巨大なライブラリを構築しました。実際、単純な反復子ベースの for ループから呼び出す (そして同じように簡単にインライン化する) ことができる単純で同等に再利用可能な関数を記述できたときに、ファンクターを記述することに多くの時間を費やしただけでした ( C++0x の範囲ベースの for ループと BOOST_FOR_EACH)。私はまだ C++ で関数型プログラミングを使用していますが、控えめにしています。2 行または 3 行のコードを 1 つにまとめるために多くのトラブルを経験し、ビルド時間を積み上げているときは、本当に自問し、深く考えなければなりません。

于 2010-06-27T15:49:05.200 に答える
0

ファンクターがこのようにコピーされている理由は、for_eachより一般的なものにするためだと思います。

参照によってファンクタを取得する実装を想像してください。ファンクターが右辺値の場合 (たとえば、別の関数から返された場合)、これは壊れます。

std::for_each(first, last, get_functor(...))

もちろん、そのような場合、アルゴリズムは const 参照によってファンクターを取得できますが (右辺値にバインドできるため)、ファンクターは const でなければなりません。

唯一の本当に一般的な解決策は、ファンクタを値で渡すことです。次に、const、non-const、右辺値、および左辺値ファンクターで動作します。

于 2010-06-26T10:08:14.160 に答える
0

コンストラクAddValueターは、コレクションの最初のメンバーの値を使用してメンバー変数を初期化するときに、intそれを構築するときにそうします。*coll.begin()theValue

これは現在修正されているため (他に何も変更されませんtheValue) 、このオブジェクトまたはこのオブジェクトのコピーtheValueから が使用されるたびに、初期化されたときと同じ値が保持されます。AddValueAddValue

これは、最初のオブジェクトが作成された時点の値であり、値*coll.begin() 変更された可能性はありません。AddValue*coll.begin()

于 2010-06-26T10:20:15.467 に答える
0

はい、それはあなたが言ったことです。ファンクターはデフォルトで値渡しされるため、std::for_each のコードにコピーされます。ただし、独自のバージョンの std::for_each を記述して、ファンクターを参照渡しすることを明示的に指定することもできます。

于 2010-06-26T02:58:19.403 に答える
0

はい。ファンクターの新しいコピーが for_each を介して渡されます。あなたが読んでいる本はこれを説明しています。

于 2010-06-26T02:58:43.443 に答える