c++20中的span

一、内存的限制和管理

在c++中,内存的管理和限制访问,怎么说都是一个道理简单但实际操作起来麻烦又难的一个问题。前面提到过字符串的string_view,那么对于普通的连续内存有没有好的方法呢?即使是加强一些都行啊。在内存操作过程中,越界和引用失效的内存地址同样是致命的。而在一块比较大的内存中,这又是很容易犯下的错误。c++新版本中有了std::span这个模板类。

二、span的定义作用

这个模板类的基本作用就是对连续内存的管理。安全性是c++标准不断迭代也不断加强的一个重要的方向和目标。可以看std::span看作一种索引,它能够保证传递的连续内存的长度的正确性。这时候儿想一想如果一个数组退化成指针后,传递给函数,长度的控制一定是一个首要的问题。一个不小心,越界的问题就出现了。看一下在c++中的具体的定义:

namespace std {
  template
  class span {
  public:
    // 常量与类型
    using element_type = ElementType;
    using value_type = remove_cv_t;
    using size_type = size_t;
    using difference_type = ptrdiff_t;
    using pointer = element_type*;
    using const_pointer = const element_type*;
    using reference = element_type&;
    using const_reference = const element_type&;
    using iterator = /* 由实现定义 */;
    using reverse_iterator = std::reverse_iterator;
    static constexpr size_type extent = Extent;
 
    // 构造函数、复制与赋值
    constexpr span() noexcept;
    template
      constexpr explicit(extent != dynamic_extent) span(It first, size_type count);
    template
      constexpr explicit(extent != dynamic_extent) span(It first, End last);
    template
      constexpr span(type_identity_t (&arr)[N]) noexcept;
    template
      constexpr span(array& arr) noexcept;
    template
      constexpr span(const array& arr) noexcept;
    template
      constexpr explicit(extent != dynamic_extent) span(R&& r);
    constexpr span(const span& other) noexcept = default;
    template
      constexpr explicit(/* 见描述 */)
        span(const span& s) noexcept;
 
    ~span() noexcept = default;
 
    constexpr span& operator=(const span& other) noexcept = default;
 
    // 子视图
    template
      constexpr span first() const;
    template
      constexpr span last() const;
    template
      constexpr span subspan() const;
 
    constexpr span first(size_type count) const;
    constexpr span last(size_type count) const;
    constexpr span subspan(
      size_type offset, size_type count = dynamic_extent) const;
 
    // 观察器
    constexpr size_type size() const noexcept;
    constexpr size_type size_bytes() const noexcept;
    [[nodiscard]] constexpr bool empty() const noexcept;
 
    // 元素访问
    constexpr reference operator[](size_type idx) const;
    constexpr reference front() const;
    constexpr reference back() const;
    constexpr pointer data() const noexcept;
 
    // 迭代器支持
    constexpr iterator begin() const noexcept;
    constexpr iterator end() const noexcept;
    constexpr reverse_iterator rbegin() const noexcept;
    constexpr reverse_iterator rend() const noexcept;
 
  private:
    pointer data_;              // 仅用于阐释
    size_type size_;            // 仅用于阐释
  };
 
  template
    span(It, EndOrSize) -> span>>;
  template
    span(T (&)[N]) -> span;
  template
    span(array&) -> span;
  template
    span(const array&) -> span;
  template
    span(R&&) -> span>>;
}

认真看一下相关的函数,其实都是用来处理数据的头尾以及相关长度的控制的,其实正好体现了定义中的对连续内存的限制管理。

三、应用

看一个具体的例子:

#include 
#include 
#include 
#include 
 
template [[nodiscard]]
constexpr auto slide(std::span s, std::size_t offset, std::size_t width) {
    return s.subspan(offset, offset + width <= s.size() ? width : 0U);
}
 
template [[nodiscard]]
constexpr bool starts_with(std::span data, std::span prefix) {
    return data.size() >= prefix.size() 
        && std::equal(prefix.begin(), prefix.end(), data.begin());
}
 
template [[nodiscard]]
constexpr bool ends_with(std::span data, std::span suffix) {
    return data.size() >= suffix.size() 
        && std::equal(data.end() - suffix.size(), data.end(), 
                      suffix.end() - suffix.size());
}
 
template [[nodiscard]]
constexpr bool contains(std::span span, std::span sub) {
    return std::search(span.begin(), span.end(), sub.begin(), sub.end()) != span.end();
//  return std::ranges::search(span, sub).begin() != span.end();
}
 
void print(const auto& seq) {
    for (const auto& elem : seq) std::cout << elem << ' ';
    std::cout << '\n';
}
 
int main()
{
    constexpr int a[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
    constexpr int b[] { 8, 7, 6 };
 
    for (std::size_t offset{}; ; ++offset) {
        constexpr std::size_t width{6};
        auto s = slide(std::span{a}, offset, width);
        if (s.empty())
            break;
        print(s);
    }
 
    static_assert(
        starts_with( std::span{a}, std::span{a, 4} ) and
        starts_with( std::span{a + 1, 4}, std::span{a + 1, 3} ) and
      ! starts_with( std::span{a}, std::span{b} ) and
      ! starts_with( std::span{a, 8}, std::span{a + 1, 3} ) and
        ends_with( std::span{a}, std::span{a + 6, 3} ) and
      ! ends_with( std::span{a}, std::span{a + 6, 2} ) and
        contains( std::span{a}, std::span{a + 1, 4} ) and
      ! contains( std::span{a, 8}, std::span{a, 9} )
    );
}

运行结果是:

0 1 2 3 4 5 
1 2 3 4 5 6 
2 3 4 5 6 7 
3 4 5 6 7 8

然后再看一个简单的例子,对照着就更好理解了:

#include 
#include 
 
int main()
{
    constexpr char str[] = "ABCDEF\n";
 
    const std::span sp{str};
 
    for (auto n{sp.size()}; n != 2; --n) {
        std::cout << sp.last(n).data();
    }
}

运行结果是:

ABCDEF
BCDEF
CDEF
DEF
EF
F

更多的技术细节可以看一下相关文档:
https://en.cppreference.com/w/cpp/container/span

四、总结

正如string_view,其实这个span也可以当做一个连续内存的view,这样理解起来就更容易,不过从实际的角度来看又有所不同。还是需要自己认真的去把相关的细节和实现仔细的翻看一下,c++的库越来越大,掌握起来也愈发的要花费更多 的精神。在早先的微软的GSL版本库中,就提供过这个模板类,其它的库也有过类似的操作,c++标准库只是吸收了这些有用的提案并重新融合。从这一点看,海纳百川,还是要谦虚谨慎,睁开眼睛看世界,不要自以为是。
做人和做技术,本质没有不同。
c++20中的span_第1张图片

你可能感兴趣的:(C++11,c++,开发语言,后端)