(CPP20) 简单实现span

文章目录

  • 视图的概念
  • 常见连续序列测试
  • 简单实现
    • Code
    • 直接引用对象
    • 传入首位置
  • END

视图的概念

在不传递引用的情况下,传递对象在很多时候会巨大的性能损耗。

而入错传递的是一个视图,这个视图能够指向原对象,那么这个直接传递的开销也是我们可以接受的。

在C++20中,std::span是一种能够指代连续序列的数据结构。

std::span - cppreference.com

具体效果如下:

#include 
#include 
#include 

struct Node {
    int x = 0;
    Node(int x) : x(x) {
        printf("[%s]=>[%p]=>[%d]\n", __func__, this, x);
    }

    Node(const Node& lhs) {
        x = lhs.x;
        printf("[%s]=>[%p=>%p]=>[%d]\n", __func__, &lhs, this, x);
    }

    operator int() {
        return x;
    }
};

void show_by_vec(std::vector<Node> arr) {
    std::cout << __func__ << std::endl;
    for (auto&& x : arr) {
        std::cout << x << ' ';
    }
    std::cout << std::endl;
}

void show_by_span(std::span<Node> arr) {
    std::cout << __func__ << std::endl;
    for (auto&& x : arr) {
        std::cout << x << ' ';
    }
    std::cout << std::endl;
}

int main() {
    std::vector<Node> arr = {1, 2, 3};
    std::cout << "++++++++++++++++++++++++++++" << std::endl;
    show_by_vec(arr);
    show_by_span(arr);
}

输出如下:

从输出可以看出,span相比较于直接传递对象不会有过多的开销

[Node]=>[000001368f2513e0]=>[1]
[Node]=>[000001368f2513e4]=>[2]
[Node]=>[000001368f2513e8]=>[3]
++++++++++++++++++++++++++++
[Node]=>[000001368f2513e0=>000001368f251400]=>[1]
[Node]=>[000001368f2513e4=>000001368f251404]=>[2]
[Node]=>[000001368f2513e8=>000001368f251408]=>[3]
show_by_vec
1 2 3
show_by_span
1 2 3

常见连续序列测试

常见的连续序列大概有以下几种

  • C式数组
  • std::vector
  • std::array

注意:

  • std::list, std::deque这些都不是连续序列
  • 特别的,变长数组虽然也是连续的,但是不能用与std::span
#include 
#include 
#include 
#include 
#include 

namespace name = std;
void show(name::span<int> arr) {
    for (auto&& elem : arr) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
}

int main() {
    // 传统的C式数组
    int c_arr[] = {1, 2, 3, 4};
    show(c_arr);                      // 直接传数组
    show({c_arr, std::size(c_arr)});  // 传递指针+长度

    // std::array
    std::array arr = {11, 22, 33, 44};
    show(arr);  // 直接传std::array

    // std::vector
    std::vector vec{111, 222, 333, 444};
    show(vec);                        // 直接传vector
    show({vec.begin(), vec.end()});   // 首尾迭代器
    show({vec.begin(), vec.size()});  // 首迭代器+长度

    // 链表不是连续序列
    // std::list lst = {1111, 2222, 3333, 4444};
    // show(list);
    
    // std::deque 也不是
}

简单实现

注意本文只是一个简单实现版本

没有考虑各种特殊情况,也没有用 SFINAE(Substitution Failure Is Not An Error)来进行约束。

一些简单的约束方法可以参考 > (C++) 一个例子,了解 SFINAE 从 cpp11 到 cpp20 的核心技巧 - 知乎 (zhihu.com)

Code

直接上代码:

#include 
#include   // cpp20 std::to_address
#include 

namespace my {

// 只考虑一个模板参数的情况
template <typename Type>
class span final {
private:
    Type*  ptr  = nullptr;
    size_t size = 0;

public:
    constexpr span() noexcept                       = default;
    constexpr span(const span&) noexcept            = default;
    constexpr span& operator=(const span&) noexcept = default;
    constexpr  ~span() noexcept                     = default;

public:  // 针对具体类型
    // 数组是无法传递对象,只能传递引用
    template <size_t N>
    constexpr span(Type (&arr)[N]) noexcept {
        this->ptr  = arr;
        this->size = N;
    }

    // 针对std::array
    template <size_t N>
    constexpr span(std::array<Type, N>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = N;
    }

    // 针对std::vector
    constexpr span(std::vector<Type>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = arr.size();
    }

public:  // 针对迭代式 使用cpp20的`std::to_address`可以同时应对迭代器和指针
    // 首位置和长度
    template <typename Iter>
    constexpr span(Iter iter, size_t length) noexcept {
        this->ptr  = std::to_address(iter);
        this->size = length;
    }

    // 首位置和尾位置
    template <typename Iter>
    constexpr span(Iter first, Iter end) noexcept {
        this->ptr  = std::to_address(first);
        this->size = end - first;
    }

public:
    constexpr Type* begin() const noexcept {
        return this->ptr;
    }
    constexpr Type* end() const noexcept {
        return this->ptr + this->size;
    }
};
}  // namespace my

测试代码与上文一致:

提示:将该模块的两份代码直接顺序复制到一个文档中即可

#include 
namespace name = my;
void show(name::span<int> arr) {
    for (auto&& elem : arr) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
}

int main() {
    // 传统的C式数组
    int c_arr[] = {1, 2, 3, 4};
    show(c_arr);                      // 直接传数组
    show({c_arr, std::size(c_arr)});  // 传递指针+长度

    // std::array
    std::array arr = {11, 22, 33, 44};
    show(arr);  // 直接传std::array

    // std::vector
    std::vector vec{111, 222, 333, 444};
    show(vec);                        // 直接传vector
    show({vec.begin(), vec.end()});   // 首尾迭代器
    show({vec.begin(), vec.size()});  // 首迭代器+长度
}

这里的主要难点就是对构造函数的实现,如何获得首地址和长度。

大致可以分为两类

  1. 直接引用对象
  2. 传入首位置

直接引用对象

关于std::vector和std::array相对比较方便,也是大家平时直接应用的方式。

而关于数组,很多人并不熟悉怎么操作。

注意Type (&arr)[N]的写法这里必须要套小括号,因为[]的优先级比&高,不然无法获得数组的引用。

std::array和普通数组一样也是使用template 。可见具有动态长度的std::vector在这里是最方便的。

public:  // 针对具体类型
    // 数组是无法传递对象,只能传递引用
    template <size_t N>
    constexpr span(Type (&arr)[N]) noexcept {
        this->ptr  = arr;
        this->size = N;
    }

    // 针对std::array
    template <size_t N>
    constexpr span(std::array<Type, N>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = N;
    }

    // 针对std::vector
    constexpr span(std::vector<Type>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = arr.size();
    }

传入首位置

首地址+长度是C语言中传数组的最常见形式。

到了C++中泛化出了迭代器的标准。为了同时适配普通指针和迭代器,在C++20中推出了std::to_address()的方法。可以多态的转化到指针中。这样就不用针对指针和迭代器写两个版本了。

关于这里的第二个传入首位位置的版本,需要允许两者直接的相互减。

请注意各种迭代器的区别:迭代器库 - cppreference.com

public:  // 针对迭代式 使用cpp20的`std::to_address`可以同时应对迭代器和指针
    // 首位置和长度
    template <typename Iter>
    constexpr span(Iter iter, size_t length) noexcept {
        this->ptr  = std::to_address(iter);
        this->size = length;
    }

    // 首位置和尾位置
    template <typename Iter>
    constexpr span(Iter first, Iter end) noexcept {
        this->ptr  = std::to_address(first);
        this->size = end - first;
    }



END

你可能感兴趣的:(C/C++,c++,c语言,c++20)