C ++ Lambdas、Capturing、Smart Ptrs、およびStack:なぜこれは機能するのですか?

私はC ++ 11の新機能のいくつかを試していましたが、動作しないと期待して次のプログラムを書こうとしました。私の驚いたことに、これは(Linux x86のGCC 4.6.1では 'std = c ++ 0x'というフラグが付きます):

#include 
#include 
#include 

std::function count_up_in_2s(const int from) {
    std::shared_ptr from_ref(new int(from));
    return [from_ref]() { return *from_ref += 2; };
}

int main() {
    auto iter_1 = count_up_in_2s(5);
    auto iter_2 = count_up_in_2s(10);

    for (size_t i = 1; i <= 10; i++)
        std::cout << iter_1() << '\t' << iter_2() << '\n'
        ;
}

返されたラムダの各実行が実行されるとき、私は 'from_ref'が削除されることを期待していました。 count_up_in_2sが実行されると、from_refはスタックからポップされますが、返されたラムダは必然的に直ちに実行されるわけではないので、返されるため、同じ参照が存在するまでラムダが実際に実行されたときにプッシュバックされるので、shared_ptrの参照カウントがゼロに達してからデータを削除しないでください。

C ++ 11のラムダキャプチャが、私がそれを信用しているよりもずっと賢明なものにしていない限り、私はそれが喜ばれるでしょう。この場合、C ++ 11の変数キャプチャは、/ something /が動的に割り当てられたメモリを処理している限り、Lispのすべての字句スコープ/クロージャのトリッキーを許可すると仮定できますか?ラムダ自体が削除されるまで、キャプチャされたすべてのリファレンスが存続すると仮定して、上記の方法でsmart_ptrsを使用できるようにすることはできますか?

これが私の考えであるとすれば、これはC ++ 11が表現力豊かな高次プログラミングを可能にするのではないのでしょうか?もしそうなら、私はC ++ 11委員会が優れた仕事をしたと思う=)

8

3 答え

ラムダは from_ref を値で取得してコピーを作成します。このコピーのため、 from_ref が破棄されると、refカウントは0ではなく、ラムダにまだ存在するコピーのため1になります。

7
追加された
@Louisいいえ、 function オブジェクトではなく、ラムダです。 std :: function はlambdasのキャプチャについて知らない。
追加された 著者 Seth Carnegie,
@ルイスはい、そうです
追加された 著者 Seth Carnegie,
@Louis - キャプチャされた変数は、コンパイラによって生成されたラムダオブジェクトのメンバ変数として格納されます。各ラムダ式は、キャプチャをメンバ(キャプチャモードに応じて参照または値による)として格納し、ラムダ本体と同等の operator()を持つカスタムクラスの生成を引き起こします。次に、このクラスをインスタンス化してラムダ式の結果値を作成します。
追加された 著者 JohannesD,
ですから、std :: functionオブジェクト自体がインスタンスの期間中にキャプチャ値を格納することを理解するのは間違いありませんか?そして、この参照を格納することによって、shared_ptr参照カウントは決して0に達しませんか?ああ、なるほど。どのようにエレガント。
追加された 著者 Louis,
したがって、参照の取得と格納は、std :: functionインスタンスにメンバーとして格納されるのではなく、特殊なケースとして処理されます。ありがとう。
追加された 著者 Louis,

以下:

std::shared_ptr from_ref(new int(from));
return [from_ref]() { return *from_ref += 2; };

これとほとんど同じです:

std::shared_ptr from_ref(new int(from));
class __uniqueLambdaType1432 {
  std::shared_ptr capture1;
 public:        
  __uniqueLambdaType1432(std::shared_ptr capture1) :
    capture1(capture1) { 
  }
  decltype(*capture1 += 2) operator ()() const {
    return *capture1 += 2;
  }
};
return __uniqueLambdaType1432(from_ref);

ここで、 __ uniqueLambdaType1432 は、語彙的に同一のラムダ式によって生成される他のラムダ型とは異なる、プログラム全体的にユニークな型です。その実際の名前はプログラマが利用できません。元のラムダ式の結果であるオブジェクトのほかに、コンストラクタが実際にコンパイラの魔法で隠されているため、他のインスタンスを作成することはできません。

4
追加された

from_refは値によって取り込まれます。

あなたの推論はあなたが代用するならば機能する

return [from_ref]() { return *from_ref += 2; };

〜と

return [&from_ref]() { return *from_ref += 2; };
1
追加された