标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。
使用allocator通常会提供更好的性能和更灵活的内存管理能力。std::allocator是标准库容器的默认内存分配器(可以替换成你的分配器,这允许你控制标准容器分配内存的方式)。
new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在一起。类似的,delete将对象析构和内存释放组合在了一起。一般情况下,将内存分配和对象构造放在一起可能会导致不必要的浪费,同时带来性能上不必要的消耗。
标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的,未构造的。
STL的分配器用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:
首先SGI STL将可用内存整块的分配,使之成为当前进程可用的内存,当程序中确实需要分配内存时,先从这些已请求好的大内存块中尝试去取得内存,如果失败的话再尝试整块的分配大内存。这种做法有效的避免了大量内存碎片的出现,提高了内存管理效率。
为了实现这种方式,STL使用了placement new,通过在自己管理的内存空间上使用placement new来构造对象,以达到原有new operator所具有的功能。
traits可以省略简单类型的析构函数调用的耗费。即像int这种类型是不需要析构函数的,可以不调用析构函数。
STL内存管理使用二级内存配置器,第一级配置器,第二级配置器
根据情况来判定,如果配置区块 大于128字节 ,说明足够大,调用 第一级配置器 。
而 小于等于128字节 ,则采用 复杂内存池(memory pool)来管理。
在STL的二级空间配置器中还多了一些机制,避免太多小区块造成的内存碎片,小区块带来的不仅是内存碎片,配置时还有额外的负担,区块越小,额外负担所占的比例就越大。
二级内存池
1、使用allocator向内存池请求size大小的内存空间,如果需要请求的内存大小 大于128 B,直接使用malloc()
2、如果需要的内存大小 小于等于128 B,allocator::allocate()根据size找到最适合的自由链表
①如果链表不为空,返回第一个node,链表头改为第二个node
②如果链表为空,使用blockAlloc,请求分配内存
③如果内存池中有一个大于一个node的空间,分配尽可能多的node(但最多20个),将一个node返回,其他的node添加到链表中。
④如果内存池只有一个node的空间,直接返回给用户
⑤若连一个node都没有,再次向操作系统请求分配内存
a.分配成功,再次进行(2的②)
b.分配失败,循环各个自由链表,寻找空间
①找到空间,再次进行(2的②)
②找不到空间,抛出异常
3、用户调用allcator::deallocate()释放内存空间,如果要求释放的内存空间大于 128 B,直接调用free()
4、否则按照其大小找到适合的自由链表,将其插入。
适配器:一种用来修饰容器、仿函数或迭代器接口的东西。例如,STL 提供的 queue 和 stack,虽然看似容器,其实只能算是一种容器适配器,因为它们的底部完全借助 deque,所有操作都由底层的 deque 供应;改变仿函数(functor)接口的,称为 function adapter等。
适配器(adapter) 在 STL 组件的灵活组合运用功能上,扮演者转换器的角色。也是一种设计模式(适配器模式)。
代码举例function adapter:
count_if(计算符合条件的个数)第三个参数是一个仿函数。minn的代码,这是有两个入参,我们希望使用minn这个仿函数作为count_if的第三个参数。所以我们需要写个配接器适配一下(将两个参数化成一个)。在STL中,提供了相关的函数 bind2nd的适配器,把第二个参数绑定让一个二元函数变成一元的。 此前定义的minn不能直接使用在count_if上,因为和count_if的pred不符合,需要继承binary_function:
template<class InputIterator, class Predicate, class Size>
void count_if(const InputIterator first, const InputIterator last, Predicate pred, Size& n)//第三个参数pred是一个仿函数
{
//计算容器内小于5的元素个数
for( ; first != last; first++)
{
if(pred(*first))//只有一个参数
++n;
}
}
template<class T>//在定义一个仿函数(functor)时不会使用binary_function,但是如果需要函数适配器便需要使用它实现:
struct minn : public binary_function<T, T, bool>{
//返回bool型值的二元函数
bool operator()(const T&x, const T&y) const{
return x < y;}
};
class Foo
{
public:
bool operator()(int i)//用一个算法判断是否小于5,是的话返回1
{
return minn<int>() (i, 5);
}
};
int main()
{
vector<int> v = {
1, 2, 3, 4, 5, 4, 3};
cout<<count_if(v.begin(), v.end(), Foo())<<endl;
return 0;
}
在我们使用STL的一些算法的时候,比如find_if等,需要使用仿函数,如果仿函数有2个参数,但是算法需要一个一元的仿函数的时候,我们可以使用适配器,比如:bind1st和bind2nd来将仿函数适配成一元的操作符。这个时候,如果仿函数是我们自己实现的,而不是STL提供的less、greater等等内置好的仿函数时,我们如果要让仿函数支持适配器,那么就必须从binary_function派生出来。
补充:该结构在C++11标准中已废弃,在C++17标准中已移除该结构,可使用该结构的最高C++标准为C++14。
binary_function可以作为一个二元函数对象的基类,它只定义了参数和返回值的类型,本身并不重载()操作符,这个任务应该交由派生类去完成。
使用实例:
#include
#include
using namespace std;
struct TCompareNumSize : public std::binary_function<int,int, int>{
int operator() (int num1, int num2)
{
return num1 < num2 ? num2 : num1;
}
};
int main()
{
TCompareNumSize oCompareSize;
int iMaxNum = oCompareSize(1,2);
std::cout<<"最大数是:"<<iMaxNum<<endl;
return 0;
}
【补充】
对应的有unary_function,unary_function可以作为一个一元函数对象的基类,它只定义了参数和返回值的类型,本身并不重载()操作符,这个任务应该交由派生类去完成。
Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中的元素,而又不需要暴露该对象的内部表示。
或者这么说:Iterator模式是运用于聚合对象的一种模式,通过运用该模式是的我们可以在** 不知道对象内部表示的情况下,按照一定顺序(由Iterator提供的方法)访问聚合对象中的各个元素**。
由于Iterator模式以上的特性:与聚合对象耦合,在一定程度上限制了它的广泛运用。一般仅用于底层聚合支持