提问人:Oğuzhan Türk 提问时间:3/2/2022 最后编辑:lubgrOğuzhan Türk 更新时间:3/2/2022 访问量:75
std::algorithm 函数 lambda 捕获多次调用
std::algorithm functions lambda capture called several times
问:
据我所知,lambda 捕获变量生命周期与 lamda 对象的生命周期绑定。例如,在本例中:
#include <string>
#include <vector>
using namespace std;
class SomeCla {
public:
constexpr SomeCla(int i, float f) noexcept : _i(i), _f(f) {}
~SomeCla() {
puts("dtor");
}
SomeCla(const SomeCla&) = default;
SomeCla(SomeCla&&) = default;
SomeCla& operator=(const SomeCla&) = default;
SomeCla& operator=(SomeCla&&) = default;
constexpr float total() const noexcept { return static_cast<float>(_i) + _f; }
private:
int _i;
float _f;
};
int main() {
vector<float> vec = { 1.0f };
const auto filler = [someCla = SomeCla(1, 2.0f)](vector<float>& someVec, int val) {
someVec.push_back(someCla.total() + static_cast<float>(val));
};
for (int i = 0; i < 10; ++i) {
filler(vec, i * 3);
}
return static_cast<int>(vec.size());
}
输出为:
dtor
“dtor”只放了一次,即使我们多次打电话给 Lamda,这是意料之中的。
但是关于std::algorithm函数有一些奇怪的事情。如果我们使用它们:
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class SomeCla {
public:
constexpr SomeCla(int i, float f) noexcept : _i(i), _f(f) {}
~SomeCla() {
puts("dtor");
}
SomeCla(const SomeCla&) = default;
SomeCla(SomeCla&&) = default;
SomeCla& operator=(const SomeCla&) = default;
SomeCla& operator=(SomeCla&&) = default;
constexpr float total() const noexcept { return static_cast<float>(_i) + _f; }
private:
int _i;
float _f;
};
int main() {
vector<float> vec = { 1.0f };
erase_if(vec, [someCla = SomeCla(1, 2.0f)](float ele) {
return ele == someCla.total();
});
puts("continue");
{
const auto filler = [someCla = SomeCla(1, 2.0f)](vector<float>& someVec, int val) {
someVec.push_back(someCla.total() + static_cast<float>(val));
};
for (int i = 0; i < 10; ++i) {
filler(vec, i * 3);
}
}
puts("continue2");
ignore = none_of(vec.cbegin(), vec.cend(), [someCla = SomeCla(1, 2.0f)](float ele) { return ele == -1.0f; });
puts("heyyyyyyyyyyyyy");
ignore = any_of(vec.cbegin(), vec.cend(), [someCla = SomeCla(1, 2.0f)](float ele) { return ele == 1.0f; });
return static_cast<int>(vec.size());
}
输出将如下所示:
dtor
dtor
dtor
dtor
dtor
dtor
dtor
continue
dtor
continue2
dtor
dtor
dtor
dtor
dtor
dtor
heyyyyyyyyyyyyy
dtor
dtor
dtor
dtor
dtor
dtor
dtor
std::erase_if 上有 7 个“dtor”,std::none_of 上有 6 个“dtor”,std::any_of 上有 7 个“dtor”,lambda 的正常调用只有 1 个“dtor”(如预期的那样)。这些数字与容器大小无关。我试过了,得到了相同的数字。
那么,问题是,它是一个错误还是依赖于 std::algorithm 函数的实现细节?看起来,这些 std::algorithm 函数可能会多次构造和破坏 lamda 对象,这就是为什么我们的 lambda 捕获变量被多次构造和销毁的原因。
顺便说一句,另一件奇怪的事情是 MSVC 构建上的这些数字(即 2)低于 GCC 和 Clang,但仍高于 1。下面是 MSVC 输出:
dtor
dtor
continue
dtor
continue2
dtor
dtor
heyyyyyyyyyyyyy
dtor
dtor
这里可以测试:https://godbolt.org/z/nWd77c9o6
目前,我决定不在调用 std::algorithm 函数时创建 lambda 捕获变量(如果它们不是基本类型),我将创建变量并在 lambda 捕获时按引用传递。
答:
我无法重现确切的输出,但可以重现 lambda 捕获中的对象被多次复制和销毁。这是由于标准算法的设计以及它留给库实现者的自由。
特别是,传递给标准算法的可调用对象是按值传递的,因此它们的复制成本很低(否则,它们可以包装在某种引用包装器中)。当将这样的对象(如您本例中的 lambda)传递给算法时,您必须期望它被传递给其他算法。由于许多标准算法是可重用的构建块,因此一种算法通常根据一种或多种其他算法来实现。当一个可调用对象被传递给这些其他算法时,它就会被复制 - 因此是你的输出。
为了完整起见,这是我可以观察到的输出:
dtor
dtor
dtor
dtor
dtor
dtor
dtor
continue
评论
只需在 lambda 的闭包中捕获 by reference 的实例,即:SomeCla
SomeCla someCla(1,2.0f);
erase_if(vec, [&someCla](float ele) {
return ele == someCla.total();
});
到处改变这一点给了我:
continue
continue2
heyyyyyyyyyyyyy
dtor
评论
erase_if(vec, [floVal = SomeCla(1, 2.0f).total()](float ele) { return ele == floVal; });
评论
erase_if