设计函数API以避免对象生命周期问题

问题描述 投票:0回答:1

我有一个返回 lambda 的函数。

auto make_scanner(std::vector<int> const &v) {
    auto begin = v.cbegin();
    auto end = v.cend();
    return [begin, end] () mutable -> int {
        return (begin != end) ? *begin++ : 0;
    };
}

请注意,lambda 将迭代器捕获到向量中,而不是向量本身。 在常见的用例中,这很好:

// Common Use Case
std::vector<int> data = {1, 2, 3};
auto scanner = make_scanner(data);
for (int x = scanner(); x != 0; x = scanner()) {
    // compute something
}

但是如果调用者从一个未命名的临时向量创建一个扫描器,那么 lambda 就会留下悬空的迭代器:

// Incorrect Use Case
auto scanner = make_scanner(std::vector<int>{1, 2, 3});
// Invoking scanner() is UB because the iterators reference
// the vector after its lifetime.

在非优化构建(即调试构建)中,即使代码错误,通常也会按预期工作。 在优化的构建中,如果幸运的话,它会崩溃或至少导致测试失败。

当使用未命名的临时向量调用 make_scanner 时,是否有办法使编译失败,同时仍然允许常见用例?

我一直在尝试使用右值引用进行各种重载,但我找不到答案。

(是的,我意识到可以使用非临时向量,但最终仍然会在 lambda 中得到悬空迭代器。我目前不关心这些情况。)

c++ lambda api-design rvalue
1个回答
0
投票

利用已删除的函数可以很容易地实现这一点。 我们需要知道的第一件事是,当重载函数时,当使用右值调用函数时,参数

T&&
优先于带有参数
const T&
的重载。

如果您随后删除右值引用重载,则重载决策将选择该函数作为右值,并且您将因使用已删除的函数而收到编译器错误。

对于你的代码,变成

auto make_scanner(std::vector<int> const &v) {
    auto begin = v.cbegin();
    auto end = v.cend();
    return [begin, end] () mutable -> int {
        return (begin != end) ? *begin++ : 0;
    };
}

auto make_scanner(std::vector<int> &&v) = delete;

您可以在这个现场示例中看到它的工作原理。

© www.soinside.com 2019 - 2024. All rights reserved.