随笔-16  评论-7  文章-0  trackbacks-0
 
hashtable:
   SGI STL的hashtable是用的开链法完成的。用一个vector储存一系列的指针,指向一个节点元素链表的头(节点元素自身维护了一个list)。
   节点定义:
template <class Value>
struct __hashtable_node
{
    __hashtable_node 
*next;
    Value val;
}
;

   迭代器摘要(没有--操作,没有逆向迭代器):
struct __hashtable_iterator
{
    
    node 
*cur;    //迭代器目前节点
    hashtable *ht;//保持对容器的关联
    
    iterator 
operator++()
    
{
        
const node *old = cur;
        cur 
= cur->next;    //如果存在结果就是它
        if(!cur)
        
{
            size_type bucket 
= ht->bkt_num(old->value);    //bkt_num()函数计算得到应该放到第几个“桶”
            while(!cur && ++bucket < ht->buckets.size())    //跳到下一个有元素的“桶”的第一个元素
                cur = ht->buckets[bucket];
        }

        
return *this;
    }

}
;


   hashtable以质数来设计表格大小,预先计算好了28个质数,大约都是两倍的关系递增,查询28个质数中,“最接近且大于元素数目”的数字作为vector的长度,如果需要重新分配,则分配下一个质数长度的vector。
   hashtable设计的容量和buckets vector的大小一样。因此如果数目超过了,就需要重新建立:新建一个temp,将原来hashtable中的元素一个一个切割出来插入到新的temp的适当位置,全部插完后交换hashtable和temp。同时释放temp的内存。
   同样,hashtable的插入操作也分为insert_unique和insert_equal,分别代表不允许重复和允许重复插入。下面是不允许重复插入操作的具体实现。

 

template <class V, class K, class HF, class Ex, class Eq, class A>
pair
<typename hashtable<V, K, HF, Ex, Eq, A>::iterator, bool>
hashtable
<V, K, HF, Ex, Eq, A>::insert_unique_norssize(const value_type& obj)
{
    
const size_type n = bkt_num(obj);    //决定其应该插入的"桶"
    node *first = buckets[n];            //first指向那个"桶"
    for(node *cur=first, cur, cur=cur->next)    //如果已有元素,则进入循环,没有则不进入
        if(equals(get_key(cur->value), get_key(obj)))    //如果已存在键值相同的
            return pair<iterator, bool>(iterator(cur, this), false);    //不插入直接返回
    
//如果无重复,则插入
    node *tmp = new_node(obj);
    temp
->next = first;
    buckets[n] 
= tmp;
    
++num_elements;
    
return pair<iterator, bool>(iterator(tmp, this), true);
}


   hashtable有一些无法处理的型别,除非用户为那些型别写了相应的hash function。

hash_set/hash_map:
   此两个跟set/map相比较,只是插入的元素不会自动排序。其他使用方式完全一致,而操作基本全部由hashtable完成。

hash_mulitset/hash_multimap:
   和multiset/multimap比较,也只是插入元素不会自动排序。
posted @ 2010-06-08 02:50 dead_horse 阅读(546) | 评论 (0)编辑 收藏
     摘要:    关联式容器:观念上类似关联式数据库(实际上简单很多),每个元素都有一个键值和一个实值,当元素插入到关联容器中时,容器内部结构(RB-tree或者hash-table)依照键值将其插入到适当的位置。   AVL tree: 一种二叉搜索树。任何节点的左右子树高度最多相差1。 如果插入后平衡被破坏,则分为以下情况: 1、 插入点...  阅读全文
posted @ 2010-06-06 16:49 dead_horse 阅读(514) | 评论 (0)编辑 收藏
4、stack
   stack是一种先进先出的数据结构。只能在栈顶操作。它以底层容器完成所有的操作,因此是一个adapter(配接器)。
template <class T, class Sequence = deque<T> >
class stack
{
    
//下面的__STL_NULL_TMPL_ARGS可以展开成为<>,这样写是为了让class template的
    
//某个具体实现与friend function template 的某个具体实现一对一关联(VC不支持?未验证)
    friend bool operator== __STL_NULL_TMPL_ARGS(const stack&const stack&):
    friend 
bool operator< __STL_NULL_TMPL_ARGS(const stack&const stack&):
public:
    typedef typename Sequence::value_type        value_type;
    typedef typename Sequence::size_type        size_type;
    typedef typename Sequence::reference        reference;
    typedef typename Sequence::const_reference  cosnt_reference;
protected:
    Sequence c;
public:
    
//利用底层容器c进行操作
}
;
   stack还有一个特性就是没有迭代器:因为只能在栈顶进行操作,所以不必要定义迭代器,也不能遍历。

5、queue
   和stack基本一致。

6、Heap
   Heap在STL中不是实际组件,只是priority的助手。
   binary heap:一种完全二叉树。可以用数组表示。如果数组的第一个元素保留(设为无穷大或者无穷西欧啊),从第二个元素开始储存,则有:对于i处的节点,其子节点必定位于2i和2i+1处,其父节点位于i/2处。(SGI STL中没有用到这个方法,因此取值方法有所改变)
   heap操作:
      push_heap:在heap中插入一个新值的时候,先把它插入到最后面(树中最下面一排的最后一个)。然后将它和父节点进行比较,如果其键值大于父节点的,就交换位置,如此一直上溯。
      pop_heap:在heap中删除最大值的时候(其实是将最大值元素交换到heap的尾部,然后--size)。先进行交换,然后对交换后的数组进行从上往下的调整:如果子节点中最大的键值大于父节点的键值,就将他们两个交换,然后继续往下进行。
      ajust_heap:上述调整方法。
(SGI STL使用的方法:先不进行交换,而是将父节点设为空洞,然后将空洞下层到符合的底层,再将前堆尾元素在空洞位置进行一次push_heap)
   sort_heap:进行N次pop_heap,就完成了一次堆排序。
   make_heap:因为所有的叶子节点都不需要调整,所以从N/2处往上进行调整。

7、priority_queue
   priority_queue是一个带有权值观念的队列,因此取出元素的时候只能取出权值最大的元素。
   priority_queue完全以底层容器为根据(缺省为vector),加上heap处理规则实现。

8、slist
   slist是一个单向链表。(SGI中有实现,但是并不在STL容器中)
   因为slist是单向的,因此对于获得slist的链表尾端需要进行一次完整的遍历,因而push_back操作十分费时,没有提供。
   slist特点:节点和迭代器设计运用了继承关系,比list要复杂。
struct __slist_node_base
{
    __slist_node_base 
*next;
}
;

template
<typename T>
struct __slist_node: public __slist_node_btose
{
    T data;
}
;


struct __slist_iterator_base
{
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef forward_iterator_tag iterator_category;    
//单向
    __slist_node_base *node;    //指向节点基本结构
}
;

template 
<typename T, typename Ref, typename Ptr>
struct __slist_iterator: public __slist_iterator_base
{
    typedef __slist_iterator
<T, T&, T*>                            iterator;
    typedef __slist_iterator
<T, const T&const T*>                const_iterator;
    typedef __slist_iterator
<T, Ref, Ptr>                        self;

    typedef T    value_type;
    typedef Ptr    pointer;
    typedef Ref reference;
    typedef __slist_node
<T> list_node;
}
;
(为什么要用继承?)
posted @ 2010-05-30 03:32 dead_horse 阅读(372) | 评论 (0)编辑 收藏
     摘要:    所谓序列式容器:其中的元素都可序但是不一定有序。   1、vector   vector的实现技术,关键在于对其大小的控制以及重新配置时的数据移动效率。   vector的迭代器实际上是普通指针。vector内部有3个iterator:start,finish,end_of...  阅读全文
posted @ 2010-05-29 02:45 dead_horse 阅读(348) | 评论 (0)编辑 收藏

  1.迭代器

  迭代器提供了一种方法,使之能够依序巡访某个容器的各个元素,而无需暴露容器的内部。

    STL的中心思想是容器和算法分开,最后再用迭代器将两者完美的融合到一起。而迭代器是实现中最关键的部分。

  迭代器是一种行为类似指针的对象,因此它必不可少的就是对operator*和operator ->的重载。可以参考auto_ptr。

 

 

  2.traits技法(重点)

  在effective C++中,条款47介绍到了traits技术。它的思想是通过提取出来某些类型的特性,然后根据它的特性进行某些操作。

  一种方法是通过内嵌类来表示类型的特性,然后在进行操作的时候进行判断,然而这种方法却不能对内置类型使用。因为我们不可能向内置类型加入内嵌类以表示它的特性。

  另一种方法表示如下(在别人blog里面看到一个类似例子,自己写了一下):

 

#include <iostream>
using namespace std;

 

/* 下面两个空结构用来表示 */

struct hot{};
struct not_hot{};

 

/* traits用来提取模版参数T的特性 */

template 
<typename T>
struct traits_taste
{
 typedef typename T::taste taste;
}
;

 

/* 在类里面要定义自身的特性 */

class Hunan
{
public:
 typedef hot taste;
}
;
class Guangdong
{
public:
 typedef not_hot taste;
}
;

 


/* food函数用traits_taste将模板参数T的特征提取出来,交由food_aux根据特征执行 */


 

template 
<typename T>
void food_aux(T pro, not_hot)
{
 cout 
<< "Food in this province is not_hot!"<<endl;
}


 

int main()
{
 cout 
<< "Hunan:";
 food(Hunan());
 cout 
<< "Guangdong:";
 food(Guangdong());
 
return 0;
}


 

这种方式通过把类的traits信息放到类的外部,从而可以通过偏特化将内置类型的traits融入进去。

 

 在STL中,迭代器对应的traits类为iterator_traits

template <class I>
struct iterator_traits
{
 typedef typename I::iterator_category iterator_category;
 typedef typename I::value_type  value_type;
 typedef typename I::difference_type difference_type;
 typedef typename I::pointer  pointer;
 typedef typename I::reference  reference;
}

为了能够表示内置类型如int *, const int *等的特性,所以要设计相应的特化版本。下例是I*的特化。

 

 

template <class I>
struct iterator_traits<I*>
{
 typedef random_access_iterator_tag iterator_category;
 typedef typename I   value_type;
 typedef typename ptrdiff_t  difference_type;
 typedef typename I
*   pointer;
 typedef typename I
&   reference;
}
;

 

从此可以看出,在设计迭代器的时候一定要提供五个内嵌的相应型别。以利于traits萃取。STL提供了一个iterators class供人继承,可以保证其符合规范。

 

template <class Category,
   
class T,
   
class Distance=ptrdiff_t,
   
class Pointer=T*,
   
class Referance=T&>
struct iterator
{
 typedef Category   iterator_category;
 typedef typename T   value_type;
 typedef typename Distance  difference_type;
 typedef typename Pointer  pointer;
 typedef typename Referance  reference;
}
;

 

template 
<class Item>
struct ListIter: public std::iterator<std::forward_iterator_tag, Item>
{
 
}

 

3. __type_traits

这个是SGI内部的,不在STL标准内。用来萃取性别的特性。
先定义两个空结构体,用来标识true和false。

struct __true_type{};
struct __false_type{};

__type_traits内则定义一些typedefs,其值为__true_type或者__false_type

template<class type>
struct __type_traits
{
    typedef __false_type has_trivial_default_constructor;
    typedef __false_type has_trivial_copy_constructor;
    typedef __false_type has_trivial_assignment_constructor;
    typedef __false_type has_trivial_destructor;
    typedef __false_type is_POD_type;
}
;

 

posted @ 2010-05-29 00:09 dead_horse 阅读(1185) | 评论 (1)编辑 收藏

空间配置器的标准接口(根据STL规范)

 

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator::rebind 
//一个嵌套的类模板

allocator::allocator()
allocator::allocator(
const allocator&)
template
<class U> allocator::allocator(const allocator<U>&//泛化的拷贝构造函数 
allocator::~allocator()

pointer allocator::address(reference x) 
const
//返回某个对象的地址.  a.address(x) 等于 &x

const_pointer allocator::address(const_reference x) 
const
//同上. 返回一个const对象的地址

pointer allocator::allocate(size_type n, 
const void* = 0 )
//分配空间, 足以存储n个T对象

void allocator::deallocate(pointer p, size_type n)
//释放空间

size_type allocator::max_size() 
const
//返回可成功分配的最大量

void allocator::construct(pointer p , const T& x)
//负责构造 相当于 new ((const void*)p) T(x)

void allocator::destroy(pointer p)
//负责析构 相当于 p->~T()

 

——————————————————————————————————————

SGI STL 的配置器与众不同, 名称是alloc而不是allocator, 而且不接受任何参数。

 

vector<int , std::allocator<int> > iv;   //in VC or CB

vector
<int , std::alloc > iv;                //in GCC

 

但是通常都是使用默认的空间配置器,而SGI STL已经为每一个容器都指定了缺省的空间配置器。所以使用的时候无太大区别。

 

template<class T, class Alloc = alloc>

class vector{ };       //缺省使用alloc

 

————————————————————————————————————————

SGI空间配置器分析:

C++的new操作符和delete操作符进行内存配置时,new:先配置内存,然后构造对象。delete:先析构对象,然后释放内存。SGI STL将内存配置、释放内存与构造、析构分开。前者由<stl_alloc.h>中的allocate()和deallocate()负责,后者由<stl_construct.h>中的construct()和destroy()负责。 这两个头文件包含于<memory>中(STL标准规定,配置器定义在memory中)。同时<memory>还包含了一个文件<stl_unitialized.h>,定义了一些全局函数用来填充、复制大块内存数据。

 

1.构造和析构。

构造:

 

template<class T1, class T2>

inline 
void construct(T1 *p, const T2& value){

new (p) T1(value);   //此处用到placement new 运算,将初始值设定到指针P所指的地方 


PS:placement new运算,并不分配内存,而是将已分配的内存调用构造函数来进行初始化。

析构:

析构函数第一个版本接受一个指针,将指针所指之物删除,可以直接调用析构函数。而第二个版本,接受两个迭代器,将其之间的所有对象全部析构。由于我们不知道这个范围的大小,有时候范围很大而每个对象的析构不是必要的,就会造成浪费。因此需要进行一个判断,根据得到的结果来判断是否需要析构它(判断用到了traits方法)。

下面是最终的析构调用情况,其中__false_type和__ture_type是两个空的struct,只用于标识,使其通过函数重载在编译的时候确定调用哪一个版本。

 

template<class ForwardIterator>

__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)

{

for(; first<last; ++first)

destroy(
&*first);

}


template
<class ForwardIterator> 

__destroy_aux(ForwardIterator first, ForwardIterator last, __ture_type)

{}

 

 同时这个析构函数对char*和wchar_t*有特化版本:将不调用他们的析构函数。(这是没有必要调用析构函数的例子么?)

2. 空间的配置和释放: std::alloc

std::alloc为了效率,设计了双层级配置器,第一级直接使用C的malloc()和free()。第二级则视配置区块的大小选择配置器,如果区块大,则直接调用第一级,如果区块过小,则采用memory pool 整理的方法。

SGI将其简单的包装了一个接口,simple_alloc类。使其符合STL规格。而在使用的时候全部使用的simple_alloc接口(缺省使用)。

 

第一级配置器:

调用malloc()和realloc(),如果配置不成功,则调用oom_malloc(), oom_realloc()(PS:oom= out of memory)。在oom_XXX的处理时,通过不断调用“内存不足处理例程”,期望在某次调用之后能够分配到。设计“内存不足处理例程”是客户端的责任。(effective C++ 对new-handler的行为有一个条款,还没认真看)

第二级配置器:

由于太多小额的区块会造成内存的碎片,同时也会带来额外的负担,因此通过第二级配置器的分配,适当的将小额内存分配出去。当区块小(<128bytes)的时候,采用内存池管理。第二级配置器维护了16个free-lists,各自管理从8~128bytes的小额区块,每一个区块以8bytes递增(将每一个小额的区块需求上调至8的倍数,以便管理)。每一次需要时候就将适当的内存从free-lists中间拔出来。客户释放后就回收到free-lists中。当需要的free-lists中没有可用的区块的时候,调用refill()函数为free-lists填充新的空间(取自内存池,用chunk_alloc()函数)。内存池的处理十分复杂,看的一头雾水。

 

SGI配置器使用方法:

 

template <class T, class Alloc = alloc>

Class vector
{

public:

       typedef T value_type;

       …

protected:

       typedef siple_alloc
<value_type, Alloc> data_allocator;

       …

}
;

 

其中第二个template参数接受缺省的alloc,可以是第一级配置器也可以是第二级配置器。SGI STL已经把它设定为第二级配置器。

 

 

3. 内存基本处理工具

在头文件<stl_uninitialized>中,定义了3个全局函数,uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(). 看到这些名字应该就知道它们是什么作用了,它们负责在已分配好的空间进行构造。这三个函数都具有”commit or rollback”语意。要么构造出所有元素,要么不构造任何东西。

在具体的实现中,这三个函数也用到了traits技法,通过traits萃取出元素的特性,如果是POD(Plain Old Data)型别,说明其拥有trivial ctor/ dtor/ copy/ assignment函数,因此可以使用copy()、fill()、fill_n()等算法,如果不是POD型别就只能够循环调用construct()函数了。这里用到的也是函数重载的方法,和上面destroy用到的方法是一样的。

注意:在unitialized_copy()实现的时候,针对char*和wchar_t*两种型别,可以采用最有效率的memmove()来复制,因此需要两份特化版本。

posted @ 2010-05-28 23:04 dead_horse 阅读(1060) | 评论 (0)编辑 收藏
仅列出标题
共2页: 1 2