2

我正在使用范围视图的管道,我想对输出进行完整的转换并保持管道运行。我知道我无法返回新范围的副本,因为它不会存在于任何地方。我不明白为什么我不能将存储保存在 lambda 的闭包中。

auto counter = []() {
    std::map<int, int> counts;
    return ranges::make_pipeable([=](auto &&rng) mutable -> std::map<int, int>& {
        // Do stuff that fills in the map
        return counts;
    });
};

auto steps = foo() | counter() | bar();
auto data = // stuff
ranges::for_each(data | steps, [](auto &&i) {
    std::cout << i << std::endl;
});

当我进行发布构建时,这是可行的,但是当我进行调试构建时,会出现段错误。如果我mapcounterlambda 中的 更改为静态并将捕获更改为引用,那么这当然可以(但显然很丑陋)。

我真正不明白的是为什么从counter(以及随之而来的闭包)返回的 lambda 的生命周期至少不如变量的生命周期长steps

4

2 回答 2

1

那是因为make_pipeable它以价值为依据。这是 src 代码中的定义:

    struct make_pipeable_fn
    {
        template<typename Fun>
        detail::pipeable_binder<Fun> operator()(Fun fun) const
        {
            return {std::move(fun)};
        }
    };

由于您是std::map<int, int>通过引用返回的,因此它指向已被移动的对象的地址。(std::move在上面的调用中)

如果这不是很清楚,请考虑一个更简单的示例。在下面的代码中,Noisy 只是一个发出调用的结构。

#include <iostream>
#include <range/v3/utility/functional.hpp>

using namespace ranges;

struct Noisy
{
   Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
   Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
   Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
   ~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }

   // global object counter
   static int cnt_;

   // local count idx
   int local_ = 0;
};
int Noisy::cnt_ = 0;

auto counter = []()
{
   Noisy n;
   return make_pipeable([=](auto x) { return n; });
};

int main(int argc, char *argv[])
{
   auto steps = counter();
   std::cout << "Deleting" << '\n';
   return 0;
}

上面代码的输出是这样的:

Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Deleting
~Noisy() dtor with local 4

如您所见,对象2(在您的情况下持有std::map<int, int>)在行打印之前被销毁Deletingsteps是一个pipeable_binder结构,它从我们传递的 lambda 扩展而来,并在最后被销毁。

于 2017-04-02T23:34:19.050 回答
0

所以我拿了@skgbanga 的代码并重新编写了它,使它更像实际情况。这表明将这些步骤应用于数据会导致管道捕获的 lambda 的额外副本。

#include <iostream>
#include <vector>
#include <range/v3/all.hpp>


struct Noisy {
    Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
    Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
    Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
    ~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }

    // global object counter
    static int cnt_;

    // local count idx
    int local_ = 0;

    // Make this a range as well. Empty is fine
    std::vector<int> range;
    auto begin() { return range.begin(); }
    auto end() { return range.end(); }
};
int Noisy::cnt_ = 0;


auto counter = []() {
    Noisy n;
    return ranges::make_pipeable([=](auto x) mutable -> Noisy& {
        std::cout << "Returning Noisy range " << n.local_ << '\n';
        return n;
    });
};


int main(int argc, char *argv[])
{
    auto steps = counter();
    std::vector<int> data{{1, 2}};
    std::cout << "Applying" << '\n';
    auto result = data | steps;
    std::cout << "Displaying" << '\n';
    ranges::for_each(result, [](auto&& v) {
        std::cout << "Local result " << v << '\n';
    });
    std::cout << "Deleting" << '\n';
    return 0;
}

输出是:

Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Applying
Noisy(Noisy&) copy ctor 5
Noisy(Noisy&) copy ctor 6
Returning Noisy range 6
~Noisy() dtor with local 6
Noisy(Noisy&) copy ctor 7
~Noisy() dtor with local 5
Displaying
Deleting
~Noisy() dtor with local 7
~Noisy() dtor with local 4

Noisy 6 是返回范围的那个,因为它是用于评估管道的副本,它几乎立即被破坏。这会导致段错误。

我希望额外的副本不应该真的发生。

于 2017-04-04T04:02:19.153 回答