(三)分配器和容器分析

目录

分配器

容器直接的关系与分类

深度探索list

迭代器设计原则和Iterator Trails的作用和设计

vector深度探索

array,foward_list深度探索

容器array

容器forward-list

deque,queue和stac深度探索

deque

queue,stack的实现

RB-tree深度探索

set,multiset深度探索

map,multimap深度探索

hashtable深度探索

unordered容器概念


 

分配器

default allocator

GI STL 的头文件defalloc.h中有一个符合标准的名为allocator的内存分配器,它只是简单地将::operator new 和::operator delete做了一层薄薄的封装。在SGI STL的容器和算法部分从来没有用到这个内存分配器。在此略过。

STL 的内存分配策略

 当用户用new构造一个对象的时候,其实内含两种操作:1)调用::operator new申请内存;2)调用该对象的构造函数构造此对象的内容

 当用户用delete销毁一个对象时,其实内含两种操作:1)调用该对象的析构函数析构该对象的内容;2)调用::operator delete释放内存

SGI STL中对象的构造和析构由::construct()和::destroy()负责;内存的申请和释放由alloc:allocate()和alloc:deallocate()负责;此外,SGI STL还提供了一些全局函数,用来对大块内存数据进行操作。

上一段提到的三大模块分别由stl_construct.h     stl_alloc.h    stl_uninitialized.h 负责

VC6.0下的allocator没有优化

// newop operator new(size_t) for Microsoft C++
#include <cstdlib>
#include <new>
#include <xutility>

_C_LIB_DECL
int __cdecl _callnewh(size_t count) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

void *__CRTDECL operator new(size_t count) _THROW1(_STD bad_alloc)
    {   // try to allocate size bytes
    void *p;
    while ((p = malloc(count)) == 0)
        if (_callnewh(count) == 0)
            {   // report no memory
            _STD _Xbad_alloc();
            }
    return (p);
    }

/*
 * Copyright (c) 1992-2007 by P.J. Plauger.  ALL RIGHTS RESERVED.
 * Consult your license regarding permissions and restrictions.
 V5.03:0009 */
  • allocator主要的两个操作是:allicatedeallocate;其里面调用operator newoperator delete
  • 没必要自己手动去写int *p=allocator<int>().allocate(512,(int*)0);和allocator<int>().deallocate(p,512)。要写明释放内存的大小,所以很难用,但是容器不需要考虑这些

VC版本如下

缺点:调用operator new 的获取内存,每次都是获取一点点内存,这样会有很多的额外开销

BC版本:

Gnu 2.9版本:

发现:这三个IDE都是通过malloc和free来分配内存,因此会带来大量的额外开销

SGI STL不使用这个allocator

STL使用alloc: (目的:尽量减少malloc次数)

16条链表,每条链表下面申请空间,额外的空间是Cookie,记录内存的大小

Gnu 4.9版下:
- 使用的allocator,没有使用alloc;为什莫不用,不知道

G4.9所附的标准库中,有许多extention allocators,其中_pool_alloc就是G2.9的alloc.

 

 

容器直接的关系与分类

  • 依缩进的方式表示复合关系,有一个关系;比如:priority_queue里面有一个heap支撑。
  • 旁边为指针大小

 

 

深度探索list

 

 

  • list Gnu2.9源代码实现
  • 注意node代码和图示的位置
  • 实现前闭后开,增加一个空白节

  • 用的分配器alloc
  • Iterator智能指针,需要知道结点node的next指针
  • 除了array和vector,其他容器的iterator都是一个class
  • iteratorclass 实现

  • 必须做至少5个typedef,很多操作符重载
  • 前++++i其中i作为对象,self& operator++(){}返回引用,这样可以进行两次前++
  • 后++ i++ self operator++(int){},阻止两次后++
  • ++++i对的++(++i);不允许两次后++ (i++)++

  • list的迭代器解引用,不像vetor直接是数据,而是(*iter).data
  • Gnu 4.9的改善地方,下图中有说明:链表指向自己

  • G4.9类之间的关系变得复杂

 

 

迭代器设计原则和Iterator Trails的作用和设计

  • Iterator需要遵循的原则,必须提供5种associated types
  • 算法向iterator提问,获得一些类型type,方便处理

问答式

Traits特性,特质

  • 算法的参数,可能是iterator或者普通指针,通过中间层traits实现
  • 算法执行时,根据传入的参数,选择相应的iterator_traits

 

标准库中还有其他的traits:type traits , char traits, allocator traits, pointer traits, array traits等 

 

vector深度探索

  • vector内存2倍扩充,是在另外的地方重新申请内存,将数据搬过去
  • 前闭后开区间

  • 2倍扩充方法,reallocation
  • insert_aux函数中再次判断,原因是可能有其他放元素的操作如insert

也要拷贝插入之后的数据,考虑到insert插入某个位置,红色框部分

vector's iterator

  • vector结点是连续的,iterator不需要是类,是个指针即可。
  • 算法问的五种相关类型,通过iterator traits实现
  • 通过指针偏特化

G4.9 vetor and iterator

 

  • 变得复杂,但是追踪其根源和G2.9本质一样。

 

 

array,foward_list深度探索

容器array

  • TR1技术报告1 ,C++1.0-1998和c++2.0-2011之间的过渡版本
  • 源码很清晰,没有构造和析构函数;申明数组大小;用指针当迭代器

G4.9变得复杂

容器forward-list

 

 

deque,queue和stac深度探索

deque

 

  • duque内存结构
  • 分段连续,用户看起来是连续的
  • 迭代器为了维持连续的状态,每次++,--都需要判断当前buffer是否用完,若用完则需要通过控制中心跳到下一个buffer

控制中心的map是指向vector的,以后也是2倍增长

deque iterator迭代器实现,关键有指向map的指针

  • deque<T>::insert讲解
  • 很好体现deque的灵活性

要判断插入位置离首尾那个近一些,插入操作涉及元素搬移

 

  • deque如何实现连续空间
  • 主要是迭代器运算符重载实现

  • 用后++(i++)调用前++(++i)
前++(++i)其中i作为对象,self& operator++(){}返回引用,这样可以进行两次前++
后++ (i++) self operator++(int){},阻止两次后++
++++i对的++(++i);不允许两次后++ (i++)++

 

  • G4.9实现
  • 控制中心实际为vector,当不足的时候,成两倍的增长,此时copy数据到新vector的中心位置,这样方便两边数据的增长

queue,stack的实现

  • 底层用deque实现,实际操作调用deque的函数
  • 有时不把queue,stack当作容器

 

  • stack,queue都不允许遍历,不提供iterator
  • stack,queue也可以选择list做底层实现,默认选择duque做底层

 queue不可选择vector做底层实现,stack也可以用vector做底层实现

编译器不会对容器做全面的检查,当没有调用错误的函数时候,编译器不会报错!

 

 

RB-tree深度探索

Red-Black tree是自平衡二叉搜索树。

  • rb_tree的封装
  • 清楚传入模板的参数列表;然后构建了一个虚空结点header
  • KeyOfValue表示怎么从value中取出key
  • identity函数(Gnu C独有)就是表示同一个数的意思

  • handle-body,采用OOP的思想,构建G4.9
  • 一个红黑树的大小为4个字节

  • test Rb_tree

 

#include <set>
#include <functional>
#include <iostream>
namespace jj31
{
void test_Rb_tree()
{
    //G2.9 vs. G2.9 : 
      //rb_tree => _Rb_tree, 
      //identity<> => _Identity<>
      //insert_unique() => _M_insert_unique()
      //insert_equal() => _M_insert_equal()
         
    cout << "\ntest_Rb_tree().......... \n";
     
    _Rb_tree<int, int, _Identity<int>, less<int>> itree;
    cout << itree.empty() << endl;  //1
    cout << itree.size() << endl;   //0

    itree._M_insert_unique(3);
    itree._M_insert_unique(8);
    itree._M_insert_unique(5);
    itree._M_insert_unique(9);
    itree._M_insert_unique(13);
    itree._M_insert_unique(5);  //no effect, since using insert_unique().
    cout << itree.empty() << endl;  //0
    cout << itree.size() << endl;   //5
    cout << itree.count(5) << endl; //1

    itree._M_insert_equal(5);
    itree._M_insert_equal(5);
    cout << itree.size() << endl;   //7, since using insert_equal().
    cout << itree.count(5) << endl; //3       
}                                                            
}

 

set,multiset深度探索

set、multiset元素的value和key合一,value就是key.

  • 容器set实现
  • const_iterator实现set不能改变容器元素的值
  • 使用identity表示set已经知道key和value是相同的

map,multimap深度探索

map/multimap的iterator不能改变key,可以改变value

  • map的结构
  • pair将key和data合成value;将key设置为const,这样通过迭代器就不会改变key的值。

select1st实现

  • map容器独特的operator[]操作,可以进行插入操作
  • 直接调用insert快一些

hashtable深度探索

  • hashtable冲突(碰撞)处理
  • rehash时,篮子扩充两倍,找到其附近的质数,重新计算元素位置
  • 内部扩充的数据已经预定好,53->97->....

  • hashtable实现
  • iterator要实现当当前node链表结束,要能进入到下一个buckets

  • hashtable使用
  • 模板参数的形式

  • 容器hashtable中hashfunction
  • hash{}的偏特化实现

 

hashtable使用

 

unordered容器概念

结构

  • test unordered_set
#include <unordered_set>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio>  //snprintf()
#include <iostream>
#include <ctime> 
namespace jj15
{
void test_unordered_set(long& value)
{
    cout << "\ntest_unordered_set().......... \n";
     
unordered_set<string> c;    
char buf[10];
            
clock_t timeStart = clock();                                
    for(long i=0; i< value; ++i)
    {
        try {
            snprintf(buf, 10, "%d", rand());
            c.insert(string(buf));                      
        }
        catch(exception& p) {
            cout << "i=" << i << " " << p.what() << endl;   
            abort();
        }
    }
    cout << "milli-seconds : " << (clock()-timeStart) << endl;      
    cout << "unordered_set.size()= " << c.size() << endl;   
    cout << "unordered_set.max_size()= " << c.max_size() << endl;  //357913941
    cout << "unordered_set.bucket_count()= " << c.bucket_count() << endl;   
    cout << "unordered_set.load_factor()= " << c.load_factor() << endl; 
    cout << "unordered_set.max_load_factor()= " << c.max_load_factor() << endl; 
    cout << "unordered_set.max_bucket_count()= " << c.max_bucket_count() << endl;           
    for (unsigned i=0; i< 20; ++i) {
        cout << "bucket #" << i << " has " << c.bucket_size(i) << " elements.\n";
    }           
    
string target = get_a_target_string();  
    {
    timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);  //比 c.find(...) 慢很多 
    cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;     
    if (pItem != c.end())
        cout << "found, " << *pItem << endl;
    else
        cout << "not found! " << endl;  
    }
 
    {
    timeStart = clock();        
auto pItem = c.find(target);        //比 std::find(...) 快很多                          
    cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;         
    if (pItem != c.end())
        cout << "found, " << *pItem << endl;
    else
        cout << "not found! " << endl;  
    }   
}                                                            
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值