MyTinySTL代码分析


代码来源:

MyTinySTL

项目介绍

MyTinySTL 是一个简化版的 STL 项目。因为本人能力有限,直接阅读 STL 项目,对于一些 cpp 特性,还有复杂代码的处理不是很娴熟,所以选择了这样一个项目当作自己第一个读源码进行分析的项目。

项目架构

整个项目可以分为下面几个大类:

  • 容器:定义好的常用数据结构
  • 迭代器:遍历容器的元素,根据不同容器使用了不同算法,实际装元素的东西
  • 算法:定义好的常用算法集合
  • 空间配置器:管理空间分配

他们之间的关系如下
总体结构

大类介绍

算法

该类定义了一些函数类和一些算法函数,函数类的类图如下:
一元函数

二元函数

这些类都是使用的 struct,作者似乎只是想用来当作一个函数使用,但是函数直接有一定联系,为了抽象,便分成了一元和二元函数.

每个类中只是重载了 () 运算符,使得调用这个类可以以类似函数的方法调用。对于用户来说,似乎就是函数,但是因为抽象了一层,使得能以多态的方式传递,并且不需要传递函数指针,甚至能成为其他类的成员变量。
例如:

// 选择函数:接受一个 pair,返回第二个元素
template <class Pair>
struct selectsecond :public unarg_function<Pair, typename Pair::second_type>
{
  const typename Pair::second_type& operator()(const Pair& x) const
  {
    return x.second;
  }
};

另外则定义了一些算法函数,以供迭代器和函数类调用。

迭代器

迭代器主要定义在iterator.h中,类图如下:
迭代器中主要分成两个大部分:

迭代器类

迭代器类
xxx_iterator_tag 可以看成对 iterator 行为的分类,iterator 才是真正的迭代器, 通过判断迭代器的类型,调用不同的方法来前进,后退,获得距离等。这样就将所有不同的迭代器抽象为 iterator 类,将行为和类解耦。
例如:

// distance 的 input_iterator_tag 的版本
template <class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance_dispatch(InputIterator first, InputIterator last, input_iterator_tag)
{
  typename iterator_traits<InputIterator>::difference_type n = 0;
  while (first != last)
  {
    ++first;
    ++n;
  }
  return n;
}

// distance 的 random_access_iterator_tag 的版本
template <class RandomIter>
typename iterator_traits<RandomIter>::difference_type
distance_dispatch(RandomIter first, RandomIter last,
                  random_access_iterator_tag)
{
  return last - first;
}

函数重写,通过输入的 iterator_category 来确定到底使用哪一个函数。

萃取迭代器类

该类主要用于提取迭代器类的种类
萃取迭代器
该类的意义在于用户如果需要创建一个迭代器 class,那么,传递一个 iteratorstruct 就能完成创建,在 class 会使用 trait 来获取迭代器的种类,确定行为。这样,就不需要建立多个 class,用于只需要通过 xxx_iterator_tag 的拼接,则可以完成五种迭代器的创建,至于细节则和 xxx_iterator_tag 绑定,更加的抽象。

整体的类图如下:
迭代器

容器

该项目中的容器大概有如下几种:

  • 序列容器:随机访问
    • basic_string:字符串
    • vector:可变长数组
    • list:链表
    • heap:堆
    • stack:栈
    • queue:队列
    • priority_queue:优先队列,使用堆优化
    • deque:双向队列
  • 关联容器:key - value 访问
    • rb_tree:红黑树
    • hashtable:哈希表,使用开链法处理冲突
    • set:集合,键值即实值,集合内元素会自动排序,键值不允许重复
    • multiset:集合,键值即实值,集合内元素会自动排序,键值允许重复
    • unordered_set:功能与用法与 set 类似,不同的是使用 hashtable 作为底层实现机制,容器中的元素不会自动排序
    • unordered_multiset:功能与用法与 multiset 类似,不同的是使用 hashtable 作为底层实现机制,容器中的元素不会自动排序
    • map:映射,元素具有键值和实值,会根据键值大小自动排序,键值不允许重复
    • multimap:映射,元素具有键值和实值,会根据键值大小自动排序,键值允许重复
    • unordered_map:功能与用法与 map 类似,不同的是使用 hashtable 作为底层实现机制,容器内的元素不会自动排序
    • unordered_multimap:功能与用法与 multimap 类似,不同的是使用 hashtable 作为底层实现机制,容器内的元素不会自动排序

容器类

其中特别介绍一下 basic_string

basic_string

看看源代码:

namespace mystl
{

using string    = mystl::basic_string<char>;
using wstring   = mystl::basic_string<wchar_t>;
using u16string = mystl::basic_string<char16_t>;
using u32string = mystl::basic_string<char32_t>;

}

可见,对于不同大小的字符类型,通过模板类的方式赋给 basic_string,这样不同的字符串的行为被抽象统一起来,具体内存大小之类的通过字符类型决定,最后影响的是空间配置器来管理内存,实现了解耦。

容器类中的容器之间没有继承关系,类图如下:

构造析构

容器类中基本都有构造函数,copy 构造函数。
例如 deque 的构造

public:
  // 构造、复制、移动、析构函数

  deque() //默认构造
  { fill_init(0, value_type()); }

  explicit deque(size_type n) //禁止隐式类型转换的带参构造
  { fill_init(n, value_type()); }

  deque(size_type n, const value_type& value) //带参构造,pass by reference
  { fill_init(n, value); }

  template <class IIter, typename std::enable_if<
    mystl::is_input_iterator<IIter>::value, int>::type = 0>
  deque(IIter first, IIter last)  // 使用 Iterator 的构造
  { copy_init(first, last, iterator_category(first)); }

  deque(std::initializer_list<value_type> ilist) //使用 list 的构造
  {
    copy_init(ilist.begin(), ilist.end(), mystl::forward_iterator_tag());
  }

  deque(const deque& rhs) // copy 构造 && pass by reference
  {
    copy_init(rhs.begin(), rhs.end(), mystl::forward_iterator_tag());
  }
  deque(deque&& rhs) noexcept
    :begin_(mystl::move(rhs.begin_)),
    end_(mystl::move(rhs.end_)),
    map_(rhs.map_),
    map_size_(rhs.map_size_)
  {
    rhs.map_ = nullptr;
    rhs.map_size_ = 0;
  }

例如 deque 的析构:

~deque()
{
  if (map_ != nullptr)
  {
    clear();
    data_allocator::deallocate(*begin_.node, buffer_size);
    *begin_.node = nullptr;
    map_allocator::deallocate(map_, map_size_);
    map_ = nullptr;
  }
}

重载

容器类基本重载了运算符,特别是很多都重载了 == 运算符

// deque 的重载
bool operator==(const deque<T>& lhs, const deque<T>& rhs)
{
  return lhs.size() == rhs.size() && 
    mystl::equal(lhs.begin(), lhs.end(), rhs.begin());
}

// list 的重载
bool operator==(const self& rhs) const { return node_ == rhs.node_; }

// map 的重载
bool operator==(const multimap<Key, T, Compare>& lhs, const multimap<Key, T, Compare>& rhs)
{
  return lhs == rhs;
}

另外,仍有不少类重载了 = 运算符,以处理自我赋值。

// list 中对 = 的重载
  list& operator=(const list& rhs)
  {
    if (this != &rhs)
    {
      assign(rhs.begin(), rhs.end());
    }
    return *this;
  }

  list& operator=(list&& rhs) noexcept
  {
    clear();
    splice(end(), rhs);
    return *this;
  }

  list& operator=(std::initializer_list<T> ilist)
  {
    list tmp(ilist.begin(), ilist.end());
    swap(tmp);
    return *this;
  }

空间配置器

空间配置器只有一个类,那就是 allocator 类。
首先,对于构造函数进行了重写,见下列代码:

template <class Ty>
void construct(Ty* ptr)
{
  ::new ((void*)ptr) Ty();
}

可见调用的不是 new 而是 ::new, 这样调用的是 operator new 而不是 new 关键字。operator new 是可以自己重载的,这样就实现了内存管理的自我控制。

那么,回到 allocator

template <class T>
void allocator<T>::construct(T* ptr)
{
  mystl::construct(ptr);
}

可见,这里的 allocator 类调用了刚才写的 construct 方法。

这里我们拿 deque 来举例.我们观察其默认构造函数

deque()
{ fill_init(0, value_type()); }

可见调用了一个叫做 fill_init 的函数,这个函数的定义为:

fill_init(size_type n, const value_type& value)
{
  map_init(n);
  if (n != 0)
  {
    for (auto cur = begin_.node; cur < end_.node; ++cur)
    {
      mystl::uninitialized_fill(*cur, *cur + buffer_size, value);
    }
    mystl::uninitialized_fill(end_.first, end_.cur, value);
  }
}

这个 mystl::uninitialized_fill 的定义如下

void  uninitialized_fill(ForwardIter first, ForwardIter last, const T& value)
{
  mystl::unchecked_uninit_fill(first, last, value, 
                               std::is_trivially_copy_assignable<
                               typename iterator_traits<ForwardIter>::
                               value_type>{});
}

最后可见 mystl::unchecked_uninit_fill 函数

template <class ForwardIter, class T>
void 
unchecked_uninit_fill(ForwardIter first, ForwardIter last, const T& value, std::false_type)
{
  auto cur = first;
  try
  {
    for (; cur != last; ++cur)
    {
      mystl::construct(&*cur, value);
    }
  }
  catch (...)
  {
    for (;first != cur; ++first)
      mystl::destroy(&*first);
  }
}

这个函数调用了 才写的 construct 函数,这里就实现了内存的自我管理和控制。

总结

这个项目的源代码给我很大的启发,特别是抽象方面,iterator 的抽象方法令我耳目一新,对于 OO 的抽象认识进步不少。

但是这个代码并不是一个特别标准的 OOP 项目,其封装性也只是体现在元素的private 保护上,就不做特别多的赘述。


Author: Dovahkiin
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Dovahkiin !
  TOC