c++20中的span

本文介绍了C++中用于内存管理的新工具std::span,其主要作用是对连续内存进行安全的限制和管理,防止越界和引用失效。span提供了对内存块头部、尾部及子视图的安全操作,确保了长度的正确性。通过示例展示了span如何在实际代码中使用,如滑动窗口、字符串前缀和后缀检查等功能。span在C++新版本中引入,旨在提升安全性,是C++标准不断迭代加强的方向之一。
摘要由CSDN通过智能技术生成

一、内存的限制和管理

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

二、span的定义作用

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

namespace std {
  template<class ElementType, size_t Extent = dynamic_extent>
  class span {
  public:
    // 常量与类型
    using element_type = ElementType;
    using value_type = remove_cv_t<ElementType>;
    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<iterator>;
    static constexpr size_type extent = Extent;
 
    // 构造函数、复制与赋值
    constexpr span() noexcept;
    template<class It>
      constexpr explicit(extent != dynamic_extent) span(It first, size_type count);
    template<class It, class End>
      constexpr explicit(extent != dynamic_extent) span(It first, End last);
    template<size_t N>
      constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept;
    template<class T, size_t N>
      constexpr span(array<T, N>& arr) noexcept;
    template<class T, size_t N>
      constexpr span(const array<T, N>& arr) noexcept;
    template<class R>
      constexpr explicit(extent != dynamic_extent) span(R&& r);
    constexpr span(const span& other) noexcept = default;
    template<class OtherElementType, size_t OtherExtent>
      constexpr explicit(/* 见描述 */)
        span(const span<OtherElementType, OtherExtent>& s) noexcept;
 
    ~span() noexcept = default;
 
    constexpr span& operator=(const span& other) noexcept = default;
 
    // 子视图
    template<size_t Count>
      constexpr span<element_type, Count> first() const;
    template<size_t Count>
      constexpr span<element_type, Count> last() const;
    template<size_t Offset, size_t Count = dynamic_extent>
      constexpr span<element_type, /* see description */> subspan() const;
 
    constexpr span<element_type, dynamic_extent> first(size_type count) const;
    constexpr span<element_type, dynamic_extent> last(size_type count) const;
    constexpr span<element_type, dynamic_extent> 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<class It, class EndOrSize>
    span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>>;
  template<class T, size_t N>
    span(T (&)[N]) -> span<T, N>;
  template<class T, size_t N>
    span(array<T, N>&) -> span<T, N>;
  template<class T, size_t N>
    span(const array<T, N>&) -> span<const T, N>;
  template<class R>
    span(R&&) -> span<remove_reference_t<ranges::range_reference_t<R>>>;
}

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

三、应用

看一个具体的例子:

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <span>
 
template<class T, std::size_t N> [[nodiscard]]
constexpr auto slide(std::span<T,N> s, std::size_t offset, std::size_t width) {
    return s.subspan(offset, offset + width <= s.size() ? width : 0U);
}
 
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool starts_with(std::span<T,N> data, std::span<T,M> prefix) {
    return data.size() >= prefix.size() 
        && std::equal(prefix.begin(), prefix.end(), data.begin());
}
 
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool ends_with(std::span<T,N> data, std::span<T,M> suffix) {
    return data.size() >= suffix.size() 
        && std::equal(data.end() - suffix.size(), data.end(), 
                      suffix.end() - suffix.size());
}
 
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool contains(std::span<T,N> span, std::span<T,M> 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 <span>
#include <iostream>
 
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++标准库只是吸收了这些有用的提案并重新融合。从这一点看,海纳百川,还是要谦虚谨慎,睁开眼睛看世界,不要自以为是。
做人和做技术,本质没有不同。
在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值