为什么我特别应该将 std::span 而不是 std::vector& 传递给函数?

Tom*_*bel 13 c++ vector c++20 std-span

我知道这可能与问题重叠:什么是 \xe2\x80\x9cspan\xe2\x80\x9d 以及我何时应该使用它?,但我认为问题的这个特定部分的答案相当令人困惑。一方面,有这样的引用:

\n
\n

如果您有一个标准库容器(或 Boost 容器等),并且您知道它最适合您的代码,请不要使用它。它无意取代其中任何一个。

\n
\n

但在同一个答案中,出现了这样的说法:

\n
\n

当您希望数据在内存中连续时,是将 const vector& 传递给函数的合理替代方案。再也不用被 C++ 大师骂了!

\n
\n

那么我没有到达哪一部分呢?我什么时候会这样做:

\n
void foo(const std::vector<int>& vec) {}\n
Run Code Online (Sandbox Code Playgroud)\n

而这是什么时候呢?

\n
void foo(std::span<int> sp) {}\n
Run Code Online (Sandbox Code Playgroud)\n

还有,这会不会

\n
void foo(const std::span<int> sp) {}\n
Run Code Online (Sandbox Code Playgroud)\n

有什么意义吗?我认为不应该,因为 astd::span只是 a struct,包含指针和长度。但是,如果它不能阻止您更改std::vector作为参数传递的值,那么它如何替换const std::vector<T>&

\n

Gui*_*cot 13

相当于传递 astd::vector<int> const&不是std::span<int> const,而是std::span<int const>。跨度本身是否为常量不会真正改变任何东西,但更多的常量无疑是一个很好的实践。

那么什么时候应该使用它呢?

我想说,这完全取决于函数的主体,您在示例中省略了函数的主体。

例如,我仍然会为此类函数传递一个向量:

std::vector<int> stored_vec;

void store(std::vector<int> vec) {
    stored_vec = std::move(vec);
}
Run Code Online (Sandbox Code Playgroud)

这个函数确实存储了向量,所以它需要一个向量。这是另一个例子:

void needs_vector(std::vector<int> const&);

void foo(std::vector<int> const& vec) {
    needs_vector(vec);
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我们需要一个向量。对于跨度,您必须创建一个新向量并因此进行分配。


对于这种函数,我会传递一个跨度:

auto array_sum(std::span<int const> const values) -> int {
    auto total = int{0};

    for (auto const v : values) {
        total += v;
    }

    return total;
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这个函数不需要向量。

即使您需要改变范围内的值,您仍然可以使用跨度:

void increment(std::span<int> const values) {
    for (auto& v : values) {
        ++v;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于 getter 之类的东西,我也倾向于使用跨度,以免暴露对类成员的直接引用:

struct Bar {
    auto get_vec() const -> std::span<int const> {
        return vec;
    }

private:
    std::vector<int> vec;
};
Run Code Online (Sandbox Code Playgroud)

  • 1. `store()` 演示了 `std::vector` 应该可以从任何具有正确类型元素的容器构造。事实并非如此,那就不好了。尽管如此,接口不应该因为目标的不足而受到负担,特别是因为有足够好的解决方法。2. `foo()` 表明,在适当的情况下不使用 `std::span` 是会传染的。它必须首先固定在叶子上。 (2认同)