0%

[gcc13] STL源码解析 std::array

std::array为11标准中带来的可以替代C风格数组的聚合类型,可以通过聚合初始化生成"C++风格"上的"C风格"数组:

1
std::array<int, 3> a = {1, 2, 3};

主要代码如下:

std::array主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template<typename _Tp, size_t _Nm>
struct __array_traits
{

using _Type = _Tp[_Nm];
using _Is_swappable = __is_swappable<_Tp>;
using _Is_nothrow_swappable = __is_nothrow_swappable<_Tp>;
};

template<typename _Tp, std::size_t _Nm>
struct array
{
typedef _Tp value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

// Support for zero-sized arrays mandatory.
typename __array_traits<_Tp, _Nm>::_Type _M_elems;
}

可以看到std::array<_Tp, _Nm> 这个template struct在_Nm不为零时实际为_Tp[_Nm]的wrapper, iterator为_Tp*,公开数据成员、没有虚基类和虚成员函数、没有定义构造函数使array成为聚合类型,可使用聚合初始化,而实际占用内存与对应C风格数组一致。

array<T,0>特化

上文提到_Nm类型参数不为零时,是由于array相比C风格数组拥有可为空集合的特性,在arrary<T,0>时其内部__array_traits拥有特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename _Tp>
struct __array_traits<_Tp, 0>
{
// Empty type used instead of _Tp[0] for std::array<_Tp, 0>.
struct _Type
{
// Indexing is undefined.
__attribute__((__always_inline__,__noreturn__))
_Tp& operator[](size_t) const noexcept { __builtin_trap(); }


// Conversion to a pointer produces a null pointer.
__attribute__((__always_inline__))
constexpr explicit operator _Tp*() const noexcept { return nullptr; }
};

using _Is_swappable = true_type;
using _Is_nothrow_swappable = true_type;
};

也就是__array_traits<_Tp, 0>实际不拥有数据成员,为空类型使array<T,0>占1字节,两个成员operator接口看着有点迷糊,我们可以来看看array的一些成员方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  // Iterators.
[[__gnu__::__const__, __nodiscard__]]
_GLIBCXX17_CONSTEXPR iterator
begin() noexcept
{ return iterator(data()); }

// Element access.
[[__nodiscard__]]
_GLIBCXX17_CONSTEXPR reference
operator[](size_type __n) noexcept
{
__glibcxx_requires_subscript(__n);
return _M_elems[__n];
}

[[__nodiscard__, __gnu__::__const__, __gnu__::__always_inline__]]
_GLIBCXX17_CONSTEXPR pointer
data() noexcept
{ return static_cast<pointer>(_M_elems); }

可以看到data方法是将实际_Tp[_Nm] static_cast为"_Tp*"后返回,operator[]则实际返回_Tp[_Nm]这个C风格数组的指定下标。这则是__array_traits<_Tp, 0>的operator[]和operator _Tp*()的用处,在调用诸如array<int,0>对象的operator[]时直接程序崩溃,调用其data()方法时返回nullptr。