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
即使我们多次调用 lamda,“dtor”也只放置了一次,这是预期的。
但是 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 表达式的闭包中通过引用捕获 SomeCla
的一个实例,即:
SomeCla someCla(1,2.0f);
erase_if(vec, [&someCla](float ele) {
return ele == someCla.total();
});
到处改这个给了我:
continue
continue2
heyyyyyyyyyyyyy
dtor
据我所知,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
即使我们多次调用 lamda,“dtor”也只放置了一次,这是预期的。
但是 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 表达式的闭包中通过引用捕获 SomeCla
的一个实例,即:
SomeCla someCla(1,2.0f);
erase_if(vec, [&someCla](float ele) {
return ele == someCla.total();
});
到处改这个给了我:
continue
continue2
heyyyyyyyyyyyyy
dtor