管道中 lambda 的意外寿命
Unexpected lifetime of lambda in pipeline
我正在使用范围视图管道,我想对输出进行完整转换并保持管道运行。我知道我不能 return 新范围的副本,因为它不会存在于任何地方。我不明白的是为什么我不能将存储保留在 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;
});
当我进行发布构建时这有效,但是当我进行调试构建时出现段错误。如果我将 counter
lambda 中的 map
更改为静态并将捕获更改为引用,那么这当然有效(但显然很难看)。
我不太明白的是,为什么从 counter
编辑的 lambda return 的生命周期(及其伴随的闭包)至少没有变量的生命周期那么长steps
.
那是因为 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>
)在行打印 Deleting
之前被销毁。 steps
是一个 pipeable_binder
结构,它从我们传递的 lambda 扩展而来,并在最后被销毁。
所以我采用了@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 是 returns 范围的那个,因为它是用于评估管道的副本,所以它几乎立即被破坏。然后这会导致段错误。
我希望额外的副本不会真的发生。
我正在使用范围视图管道,我想对输出进行完整转换并保持管道运行。我知道我不能 return 新范围的副本,因为它不会存在于任何地方。我不明白的是为什么我不能将存储保留在 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;
});
当我进行发布构建时这有效,但是当我进行调试构建时出现段错误。如果我将 counter
lambda 中的 map
更改为静态并将捕获更改为引用,那么这当然有效(但显然很难看)。
我不太明白的是,为什么从 counter
编辑的 lambda return 的生命周期(及其伴随的闭包)至少没有变量的生命周期那么长steps
.
那是因为 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>
)在行打印 Deleting
之前被销毁。 steps
是一个 pipeable_binder
结构,它从我们传递的 lambda 扩展而来,并在最后被销毁。
所以我采用了@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 是 returns 范围的那个,因为它是用于评估管道的副本,所以它几乎立即被破坏。然后这会导致段错误。
我希望额外的副本不会真的发生。