概述

本文模仿SGI STL对红黑树进行封装,以实现简单的 set 和 map。本文的目的不是造一个更好的轮子,而是加深对C++封装与泛型技法的体会与理解。阅读本文之前,建议先对红黑树进行学习。本文是以rb_tree的实现为基础的,建议与关联式数据结构_红黑树剖析 #C++进行结合阅读。

map

因为map的实现更具代表性,所以先对map进行讨论。

map的所有元素都会根据的元素的键值自动被“排序”,通过对红黑树的了解,这种“排序”其实是通过对二叉搜索树的中序遍历体现的。map的所有元素类型都是pair,pair的第一个成员为键值(Key),第二个成员为实值(Value),map不允许两个元素具有相同的键值。

MapKeyOfT

由于在rb_tree层面,实际的数据类型都被视为模板参数 T,所以对于map来说,key的拿取变得困难。仿函数MapKeyOfT的设计便是是为了取出map元素的键值,即pair的第一个成员,以便于根据键值维系红黑树的结构。

struct MapKeyOfT
{
  const Key& operator()(const std::pair<const Key, Value>& kv)
  {
    return kv.first; //返回pair的第一个成员
  }
};

在rb_tree层面,MapKeyOfT被视为模板参数KeyOfT,以同时兼容map和set,而不必关心rb_tree的具体使用者及具体函数的内部实现细节。

迭代器

对于map,可以通过迭代器修改元素的Value,但不允许通过迭代器修改元素的Key,这是因为任意修改Key会破坏底层红黑树的组织。map的迭代器直接对rb_tree进行复用即可。

public:
	typedef typename RB_tree<Key, std::pair<const Key, Value>, MapKeyOfT>::iterator iterator;
	typedef typename RB_tree<Key, std::pair<const Key, Value>, MapKeyOfT>::const_iterator const_iterator;

public:
  iterator begin()
  {
    return _tree.begin();
  }

  iterator end()
  {
    return _tree.end();
  }

  const_iterator begin() const
  {
    return _tree.begin();
  }

  const_iterator end() const
  {
    return _tree.end();
  }

在对map进行插入操作时,不存在迭代器失效问题;进行删除操作(这里没有实现)时,被删除的元素迭代器失效

insert接口

map的insert接口直接对rb_tree进行复用。

std::pair<iterator, bool> insert(const std::pair<Key, Value>& kv)
{
  return _tree.insert(kv);
}

operator[]

operator[] 是map独有的一个接口,可以支持用户通过Key方便地对其Value进行修改。operator[]的底层本质是对红黑树insert接口的复用,通过insert的返回值拿到对应元素的迭代器,并通过迭代器拿到元素的Value并返回。

Value& operator[](const Key& key)
{
  std::pair<iterator, bool> ret = _tree.insert(std::make_pair(key, Value()));
  return ret.first->second; //通过迭代器返回元素的Value引用
}

const Value& operator[](const Key& key) const
{
  std::pair<iterator, bool> ret = _tree.insert(std::make_pair(key, Value()));
  return ret.first->second; //通过迭代器返回元素的Value的const引用
}

set

和map一样,set中的所有元素都会根据元素的键值被自动排序。set的元素不像map那样同时拥有Key和Value,可以认为,set的Key就是Value,Value就是Key,set不允许两个元素具有相同的键值。

SetKeyOfT

对于set的KeyOfT仿函数,只需要直接返回元素的Key值即可。set的KeyOfT的设计目的完全是为了迁就map,以进行兼容。

struct SetKeyOfT
{
  const Key& operator()(const Key& key)
  {
    return key;
  }
};

迭代器

不能通过set的迭代器修改set的键值,因为如果任意修改元素的键值,会破坏底层红黑树的结构。为了杜绝写入操作,可以直接将rb_tree的const迭代器作为set的迭代器,即set的迭代器是一种const iterators

public:
	//set的普通迭代器和const迭代器都是红黑树的const迭代器
  typedef typename shr::RB_tree<Key, Key, SetKeyOfT>::const_iterator iterator;
  typedef typename shr::RB_tree<Key, Key, SetKeyOfT>::const_iterator const_iterator;

public:
  iterator begin() const
  {
    return _tree.begin();
  }

  iterator end() const
  {
    return _tree.end();
  }

insert接口

set的insert接口不能对rb_tree直接复用,这是因为存在返回值类型不一致的问题。正如上文所说,set的普通迭代器和const迭代器其实都是rb_tree的const迭代器,在set层面,insert的返回值本质是pair<typename RB_tree<Key, Key, SetKeyOfT>::const_iterator, bool>,而rb_tree的返回值类型是pair<typename RB_tree<Key, Key, SetKeyOfT>::iterator, bool>,两个pair对象的类型不同,直接复用会发生报错。

解决方法是,在rb_tree的迭代器中增加一个构造函数:

template<typename T, typename Ref, typename Ptr>
class __RB_tree_iterator
{
private:
	typedef __RB_tree_iterator<T, T&, T*> iterator;

public:
  __RB_tree_iterator(const iterator& it)
    :_node(it._node)
    { }
  
  	/*…………*/
}

如果__RB_tree_iterator类是一个普通迭代器,则该构造函数是一个拷贝构造;如果__RB_tree_iterator类是一个const迭代器,则该构造函数是一个普通构造。

完成这个特殊的构造函数之后,就可以在set的insert接口中,将rb_tree的insert接口的返回值做一层转换,即可顺利返回。这其实是一种用普通迭代器构造const迭代器的行为。

std::pair<iterator, bool> insert(const Key& k)
{
  //先接收rb_tree的返回值
  std::pair<typename RB_tree<Key, Key, SetKeyOfT>::iterator, bool> p = _tree.insert(k);
  //用普通迭代器构造const迭代器
  return std::pair<iterator, bool>(p.first, p.second);
}

总结

对红黑树的实现、以及本文对红黑树的封装均与SGI STL的源码有些不同,是对SGI STL的简化版,但大体结构是类似的。正如文章开头所说,本文的目的不是造一个更好的轮子,而是学习泛型技法的内涵与STL的学理。

基于rb_tree对STL set&map的封装 #C++_rb_tree

一如侯捷老师所说,“参观飞机工厂不能让你学得流体力学,也不能让你学会开飞机。但如果你既会开飞机又懂流体力学,参观飞机工厂可以给你带来最大的乐趣和价值”。