重要网页

www.cplusplus.com www.cppreference.com www.gcc.gnu.org

STL的六大部件

1)容器(Containers):存放我们要操作的数据,可以是数字、对象等; 2)分配器(Allocators):容器需要占用内存,容器占用的内存由分配器分配; 3)算法(Algorithms):被独立出来的模板函数,用来操作容器,包括常见的排序算法、查找算法等; 4)迭代器(Iterators):算法既然要操作容器中的数据,需要有工具访问容器中数据,那就是迭代器,是一种泛化的指针; 5)适配器(Adapters):一些容器底层和数据操作具有一定的相似,所以一些容器使用其他容器作为底层数据结构, 将其他容器的函数转换为自己的函数; 6)仿函数(Functors):实际上是类中的operator()小括号运算符重载函数,存在类似函数的行为。 STL六大部件关系图

容器结构分析

容器结构分析

容器测试

1、array(数组)

#include <iostream>
#include <array>
#include <ctime>
#include <algorithm>
#include <cstdlib>

#define use_sequence_find 1
#define use_qsort_bserach_find 1

namespace test_array {
	/*
	CompareLong是作为qsort、bsearch的比较函数
	*/
	int CompareLong(void const* l, void const* r) {
		return *(long*)l < *(long*)r;
	}

	void test() {
		long target = 23456;
		clock_t	curT;
		const long ARRAY_SIZE = 100000;
		srand(time(0));
		std::array<long, ARRAY_SIZE> myArray;

		curT = clock();
		for (long i = 0; i < ARRAY_SIZE; ++i) {
			myArray[i] = rand();
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
		使用顺序查找find对容器元素进行查找
		*/
#if use_sequence_find
		curT = clock();
		auto iter = std::find(myArray.begin(), myArray.end(), target);
		std::cout << "find element use time " << clock() - curT << std::endl;

		if (iter != myArray.end())
			std::cout << "find target " << target << " index in " << iter - myArray.begin() << std::endl;
		else
			std::cout << "find target " << target << " error!" << std::endl;
#endif

	/*
	使用<stdlib>的库函数qsort、bsearch函数对容器元素进行排序和查找
	*/
#if use_qsort_bserach_find
		curT = clock();
		qsort(myArray.data(), ARRAY_SIZE, sizeof(long), CompareLong);
		std::cout << "qsort use time " << clock() - curT << std::endl;

		curT = clock();
		bsearch(&target, myArray.data(), ARRAY_SIZE, sizeof(long), CompareLong);
		std::cout << "bsearch use time " << clock() - curT << std::endl;
#endif
	}
}

控制台输出结果如下:

insert element use time 13
find target 23456 index in 5723
qsort use time 9
bsearch use time 0
find element use time 0
请按任意键继续. . .

2、vector(可扩展数组)

#include <iostream>
#include <vector>
#include <ctime>
#include <algorithm>
#include <string>
#include <cstdlib>
namespace test_vector {
	int CompareString(void const* l, void const* r) {
		return *(std::string const*)l < *(std::string const*)r;
	}

	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long VECTOR_SIZE = 100000;
		std::vector<std::string> myVec;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < VECTOR_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myVec.push_back(std::string(buf));
			} catch (std::exception& p) { // 当内存不够分配的情况下,会抛出bad_alloc异常
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort(); //退出程序
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;

#define use_sequence_find 1
#define use_sort_bsearch_find 1
#define use_sort_find 1
#define use_qsort_find 0

#if use_sequence_find
		/*
			使用顺序遍历find查找元素
		*/
		curT = clock();
		auto iter = std::find(myVec.begin(), myVec.end(), target);
		std::cout << "find target " << target << " use time " << clock() - curT << std::endl;
#endif

#if use_sort_bsearch_find
		/*
			使用sort、bsearch查找元素
		*/
#if use_sort_find
		curT = clock();
		std::sort(myVec.begin(), myVec.end());
		std::cout << "sort use time " << clock() - curT << std::endl;
#endif

#if use_qsort_find
		curT = clock();
		qsort(myVec.data(), myVec.size(), sizeof(std::string), CompareString);
		std::cout << "qsort use time " << clock() - curT << std::endl;
#endif

		curT = clock();
		bsearch(&target, myVec.data(), myVec.size(), sizeof(std::string), CompareString);
		std::cout << "bsearch use time " << clock() - curT << std::endl;
#endif
	}
}

当使用qsort函数对vector进行排序时,控制台输出信息如下:

insert element use time 1550
find target 23456 use time 10
qsort use time 135
bsearch use time 0
请按任意键继续. . .

当使用sort函数对vector进行排序时,控制台输出信息如下:

insert element use time 1510
find target 23456 use time 4
sort use time 3585
bsearch use time 0
请按任意键继续. . .

根据上述两种排序函数的用时发现一个很有趣的东西,qsort排序的性能优于sort排序大约20倍(已测试多次结果均如此,而且数据量越大比较越明显),这种性能提升是非常优越的!所以我建议排序查找尽量使用qsort+bsearch的方式。

3、list(双向链表)

#include <iostream>
#include <list>
#include <ctime>
#include <string>
namespace test_list {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long LIST_SIZE = 100000;
		std::list<std::string> myList;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < LIST_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myList.push_back(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
			使用顺序遍历find查找元素
		*/
		curT = clock();
		auto iter = std::find(myList.begin(), myList.end(), target);
		std::cout << "find target " << target << " use time " << clock() - curT << std::endl;

		/*
			使用list的成员函数sort来排序list
			(我们不能使用algorithm的sort函数对list进行排序,\
				因为在algorithm的sort函数里面用到了iterator的加减乘除操作,\
				但是在list数据结构中,这些泛化指针不是连续的)
		*/
		curT = clock();
		myList.sort();
		std::cout << "sort use time " << clock() - curT << std::endl;
	}
}

使用list容器特别注意的一点就是不能使用algorithm提供的sort函数对list容器进行排序,而是直接使用容器类内置sort函数进行排序!

4、forward_list(单向链表)

#include <iostream>
#include <forward_list>
#include <ctime>
#include <string>
namespace test_forward_list {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long LIST_SIZE = 100000;
		std::forward_list<std::string> myList;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < LIST_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myList.push_front(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
		使用顺序遍历find查找元素
		*/
		curT = clock();
		auto iter = std::find(myList.begin(), myList.end(), target);
		std::cout << "find target " << target << " use time " << clock() - curT << std::endl;

		/*
		使用forward_list容器类的成员函数sort来排序forward_list
		*/
		curT = clock();
		myList.sort();
		std::cout << "sort use time " << clock() - curT << std::endl;
	}
}

forward_list容器的使用和list容器使用类似,不同之处在于forward_list是单向的,故而在插入/删除元素的时候只能在首端插入/删除元素。

5、deque(双端队列)

#include <iostream>
#include <deque>
#include <ctime>
#include <algorithm>
#include <string>
namespace test_deque {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long DEQUE_SIZE = 100000;
		std::deque<std::string> myDeque;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < DEQUE_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myDeque.push_back(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
		使用顺序遍历find查找元素
		*/
		curT = clock();
		auto iter = std::find(myDeque.begin(), myDeque.end(), target);
		std::cout << "find target " << target << " use time " << clock() - curT << std::endl;

		/*
		使用algorithm的sort函数对deque容器进行排序
		*/
		curT = clock();
		sort(myDeque.begin(), myDeque.end());
		std::cout << "sort use time " << clock() - curT << std::endl;
	}
}

6、stack(栈)

#include <iostream>
#include <stack>
#include <ctime>
#include <string>
namespace test_stack {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long STACK_SIZE = 100000;
		std::stack<std::string> myStack;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < STACK_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myStack.push(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
	}
}

stack是一种特殊的容器适配器,其实现内部是默认持有一个deque,并且保持元素先进后出的特性,因此stack容器内不存在迭代器(Iterators),故而我们不可以去排序一个stack。

7、queue(队列)

#include <iostream>
#include <queue>
#include <ctime>
#include <string>
namespace test_queue {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long STACK_SIZE = 100000;
		std::queue<std::string> myQueue;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < STACK_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myQueue.push(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
	}
}

queue是一种特殊的容器适配器,其实现内部是默认持有一个deque,并且保持元素先进先出的特性,因此queue容器内不存在迭代器(Iterators),故而我们不可以去排序一个queue。

8、multiset

#include <set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_multiset {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long MULTISET_SIZE = 100000;
		std::multiset<std::string> myMultiset;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < MULTISET_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myMultiset.insert(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_set_find 1
#define use_algorithm_find 0
		/*
		使用set容器类成员函数查找目标值
		*/
#if use_set_find
		curT = clock();
		myMultiset.find(target);
		std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
		/*
		使用algorithm提供的find查找目标值
		*/
#if use_algorithm_find
		curT = clock();
		std::find(myMultiset.begin(), myMultiset.end(), target);
		std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

9、multimap

#include <map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_multimap {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long MUTIMAP_SIZE = 100000;
		std::multimap<std::string, long> myMultimap;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < MUTIMAP_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myMultimap.insert(std::pair<std::string, long>(std::string(buf), i));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;

		/*
		multimap查找key下的所有value的三种方法
		*/
		std::cout << "use find() + count() ... " << std::endl;
#if 1	// 使用find结合count
		int num = myMultimap.count(target);
		auto iter = myMultimap.find(target);
		for (int i = 0; i < num; ++i,++iter){
			std::cout << "key = " << iter->first << " ; value = " << iter->second << std::endl;
		}
#endif

		std::cout << "use equal_range() ... " << std::endl;
#if 1	// 使用equal_range函数
		auto pair = myMultimap.equal_range(target);
		while (pair.first != pair.second) {
			std::cout << "key = " << pair.first->first << " ; value = " << pair.first->second << std::endl;
			pair.first++;
		}
#endif

		std::cout << "use lower_bound() + upper_bound() ... " << std::endl;
#if 1	// 使用lower_bound和upper_bound函数
		auto beg = myMultimap.lower_bound(target);
		auto end = myMultimap.upper_bound(target);
		while (beg != end) {
			std::cout << "key = " << beg->first << " ; value = " << beg->second << std::endl;
			beg++;
		}
#endif
	}
}

multimap查找元素的三种方法: 1)find() + count() 2)equal_range() 3)low_bound() + upper_bound()

10、set

#include <set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_set {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long SET_SIZE = 100000;
		std::set<std::string> mySet;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < SET_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				mySet.insert(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_set_find 1
#define use_algorithm_find 0
		/*
		使用set容器类成员函数查找目标值
		*/
#if use_set_find
		curT = clock();
		mySet.find(target);
		std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
		/*
		使用algorithm提供的find查找目标值
		*/
#if use_algorithm_find
		curT = clock();
		std::find(mySet.begin(), mySet.end(), target);
		std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

根据多次试验发现使用容器set的成员函数find比algorithm的find函数的查找速度要快。 此外,multiset和set的区别在于:multiset允许元素重复,而set不允许元素重复。

11、map

#include <map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_map {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long MAP_SIZE = 100000;
		std::map<std::string, long> myMap;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < MAP_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myMap.insert(std::pair<std::string, long>(std::string(buf), i));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_map_find 1
#define use_algorithm_find 1
		/*
		使用map容器类成员函数查找目标值
		*/
#if use_map_find
		curT = clock();
		myMap.find(target);
		std::cout << "map find target " << target << " use time " << clock() - curT << std::endl;
#endif
		/*
		使用algorithm提供的find查找目标值
		*/
#if use_algorithm_find
		curT = clock();
		std::find(myMap.begin(), myMap.end(), target);
		std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

map不允许使用algorithm的find函数! 此外,multimap和map的区别在于:multimap允许元素键key重复,而map不允许元素键key重复; 当map在insert过程中遇到重复的key时,此时的操作是忽略而不是替换。

12、unordered_multiset

#include <unordered_set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_multiset {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long UNODEREDMULTISET_SIZE = 100000;
		std::unordered_multiset<std::string> myUnorderedMultiSet;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < UNODEREDMULTISET_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myUnorderedMultiSet.insert(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_unordered_multiset_find 1
#define use_algorithm_find 1
		/*
		使用unordered_multiset容器类成员函数查找目标值
		*/
#if use_unordered_multiset_find
		curT = clock();
		myUnorderedMultiSet.find(target);
		std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
		/*
		使用algorithm提供的find查找目标值
		*/
#if use_algorithm_find
		curT = clock();
		std::find(myUnorderedMultiSet.begin(), myUnorderedMultiSet.end(), target);
		std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

13、unordered_multimap

#include <unordered_map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_multimap {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long UNORDEREDMULTIMAP_SIZE = 100000;
		std::unordered_multimap<std::string, long> myUnorderedMultiMap;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < UNORDEREDMULTIMAP_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myUnorderedMultiMap.insert(std::pair<std::string, long>(std::string(buf), i));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
		使用unordered_multimap容器类成员函数查找目标值
		*/
#define use_unordered_multimap_find 1
#if use_unordered_multimap_find
		curT = clock();
		myUnorderedMultiMap.find(target);
		std::cout << "map find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

14、unordered_set

#include <unordered_set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_set {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long UNOREDEREDSET_SIZE = 100000;
		std::unordered_set<std::string> myUnorderedSet;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < UNOREDEREDSET_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myUnorderedSet.insert(std::string(buf));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_unordered_set_find 1
#define use_algorithm_find 1
		/*
		使用unordered_set容器类成员函数查找目标值
		*/
#if use_unordered_set_find
		curT = clock();
		myUnorderedSet.find(target);
		std::cout << "unordered_set find target " << target << " use time " << clock() - curT << std::endl;
#endif
		/*
		使用algorithm提供的find查找目标值
		*/
#if use_algorithm_find
		curT = clock();
		std::find(myUnorderedSet.begin(), myUnorderedSet.end(), target);
		std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

15、unordered_map

#include <unordered_map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_map {
	void test() {
		clock_t curT;
		std::string target = "23456";
		srand(time(0));
		const long UNORDEREDMAP_SIZE = 100000;
		std::unordered_map<std::string, long> myUnorderedMap;
		char buf[10] = { '\0' };
		curT = clock();
		for (long i = 0; i < UNORDEREDMAP_SIZE; ++i) {
			try {
				sprintf_s(buf, 10, "%d", rand());
				myUnorderedMap.insert(std::pair<std::string, long>(std::string(buf), i));
			}
			catch (std::exception& p) {
				std::cout << "i=" << i << " " << p.what() << std::endl;
				abort();
			}
		}
		std::cout << "insert element use time " << clock() - curT << std::endl;
		/*
		使用map容器类成员函数查找目标值
		*/
#define use_map_find 1
#if use_map_find
		curT = clock();
		myUnorderedMap.find(target);
		std::cout << "myUnorderedMap find target " << target << " use time " << clock() - curT << std::endl;
#endif
	}
}

模型的泛化和特化

空间配置器Allocators

1、vc++、CB适配器:allocator

调用了operartor new和operator delete,底层调用的是malloc和free,除了需要存储数据的内存还需要额外的开销(cookie表示元素大小,消耗8个字节),因此不适合小内存的分配

2、gcc\g++ alloc配置器:

设计了双级配置器: 1)一级分配器:当申请内存大小 size > 128bytes,直接启动一级适配器,直接调用malloc 和 free 2)二级分配器:当申请内存大小 < 128bytes时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。这种做法有两个优点: a、小对象的快速分配。小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题时,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。 b、避免了内存碎片的生成。程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。

3、alloc两级配置器实现

1)Level-1_alloc 一级配置器实现: 一级配置器

template <class T>
class malloc_alloc {
private:
	static coom_handler_type oom_handler;
	static T* oom_malloc(size_t) {
		coom_handler_type my_malloc_handler;
		void *result;
		for (;;)
		{
			my_malloc_handler = oom_handler;
			if (0 == my_malloc_handler)
			{
				std::cerr << "out of memory" << std::endl;
				exit(1);
			}
			(*my_malloc_handler)();
			result = malloc(n);
			if (result)
			{
				return(result);
			}
		}
	};
	static T* oom_realloc(T*, size_t) {
		coom_handler_type my_malloc_handler;
		void *result;
		for (;;)
		{
			my_malloc_handler = oom_handler;
			if (0 == my_malloc_handler)
			{
				std::cerr << "out of memory" << std::endl;
				exit(1);
			}
			(*my_malloc_handler)();
			result = realloc((char*)p, n);
			if (result)
			{
				return(result);
			}
		}
	};
public:
	// allocate成员函数用来分配内存
	static T* allocate(size_t n) {
		// 直接使用<stdlib.h>的库函数malloc分配内存
		T* res = (T*)malloc(n);
		// 如果分配内存失败,则调用OOM(out of memory)处理
		if (res == nullptr) {
			res = oom_malloc(n);
		}
		return res;
	};
	// deallocate成员函数用来释放内存
	static void deallocate(T* p) {
		// 直接使用<stdlib.h>的库函数free释放内存
		free((void*)p);
	};
	// reallocate成员函数用来重新分配内存
	static T* reallocate(T* p, size_t new_size) {
		// 直接使用<stdlib.h>的库函数realloc重新分配内存
		T* res = realloc((void*)p, new_size);
		// 如果重新分配内存失败,则调用OOM(out of memory)处理
		if (res == nullptr) {
			res = oom_realloc(p, new_size);
		}
		return res;
	};
	// 设置函数指针
	static coom_handler_type set_malloc_handler(coom_handler_type f)
	{
		coom_handler_type old = oom_handler;
		oom_handler = f;
		return(old);
	}
};
// 静态变量初始化
template <class T>
coom_handler_type malloc_alloc<T>::oom_handler = (coom_handler_type)0;

2)Level-2_alloc 二级配置器实现: 二级配置器

//内存对齐最小字节
enum { __ALIGN = 8 };
// 内存池中最大内存节点 128
enum { __MAX_BYTES = 128 };
// 内存
enum { __NFREELISTS = __MAX_BYTES / __ALIGN };

// 空闲块列表节点
union cnode_alloc_obj
{
	union cnode_alloc_obj* free_list_link;
	char client_data[1]; // info address 
};
template <class T>
class default_alloc {
	typedef cnode_alloc_obj obj;
private:
	// 内存字节对齐 8的倍数
	static size_t  round_up(size_t bytes)
	{
		return ((bytes + __ALIGN - 1) &~ (__ALIGN - 1));
	}
	// 内存字节数组的下标
	static size_t free_list_index(size_t bytes)
	{
		return ((bytes + __ALIGN - 1) / __ALIGN - 1);
	}
	// 创建空闲链表并返回第一个可用内存块地址,并将链表下一个结点地址挂在空闲块列表表头数组上
	static T* refill(size_t n) {
		// 预设置一个一次性分配小内存的空闲块数nobjs
		int nobjs = 20, i;
		// 通过chunk_alloc函数传入nobjs引用,返回大内存的首地址(nobjs会随着空闲内存大小改变)
		char* chunk = chunk_alloc(n, nobjs);
		obj** my_free_list;
		obj* result, *current_obj, *next_obj;
		// 如果块数为1,则直接返回空闲块内存地址
		if (1 == nobjs)
			return (T*)chunk;
		// 若空闲块数不为1,返回第一块空闲内存的地址,并将后续的空闲块链接起来
		my_free_list = free_list + free_list_index(n);
		result = (obj*)chunk;
		*my_free_list = next_obj = (obj*)(chunk + n);
		while (nobjs-- > 2) {
			current_obj = next_obj;
			next_obj = (obj*)((char*)current_obj + n);
			current_obj->free_list_link = next_obj;
		}
		next_obj->free_list_link = nullptr;
		return (T*)result;
	};
	// 根据传入引用参数nobjs以及自身空闲内存调整分配内存大小
	static char* chunk_alloc(size_t size, int & nobjes) {
		char* res;
		// 所需内存大小
		size_t total_bytes = size * nobjs;
		// 自身剩余空闲内存大小
		size_t bytes_left = end_free - start_free;
		// 若空闲内存大小大于所需内存大小,则直接返回空闲内存首地址,并移动start_free指针
		if (bytes_left >= total_bytes) {
			res = start_free;
			start_free += total_bytes;
			return res;
		}
		// 若空闲内存不能满足 nobjs 个 size 大小的内存,但是至少能满足一个或多个 size,则动态修改 nobjs,并返回空闲内存首地址,并移动start_free指针
		else if (bytes_left >= size) {
			nobjs = bytes_left / size;
			total_bytes = size * nobjs;
			res = start_free;
			start_free += total_bytes;
			return res;
		}
		// 若空闲内存不能满足一个 size 大小的内存,则需要另外申请一块大内存
		else {
			// 计算所需内存大小数(一般是 total_bytes 的两倍 + 附加量)
			// 附加量 :一个随着配置次数增加而愈来愈大的附加量
			// 加这个值的作用是起到一个附加值的作用,毕竟我们扩容如果只是简单的扩容到需求的两倍不够灵活
			// 倘若加上这个附加量,这个附加量的值是根据原先内存池的大小右移位4位来生成的,就会更加灵活
			size_t bytes_to_get = 2 * total_bytes + round_up(heap_size >> 4);
			// 如果自身剩余空闲内存还有空间,则将这些空间放置空闲块链表当中
			if (bytes_left > 0) {
				obj** my_free_list = free_list + free_list_index(bytes_left);
				((obj*)start_free)->free_list_link = *my_free_list;
				*my_free_list = (obj *)start_free;
			}
			// 另外找一块大内存重新移动start_free和end_free指针
			start_free = (char *)malloc_alloc::allocate(bytes_to_get);
			// 如果分配失败了的话,我们还可以去比size大的空闲块链表找找有没有空闲块,如果有也可以返回这一块空闲块地址。
			if (start_free == nullptr) {
				int i;
				obj** my_free_list, *p;
				// 从 size 开始遍历至 _MAX_BYTES 
				for (i = size; i <= _MAX_BYTES; I += _ALIGN) {
					my_free_list = free_list + free_list_index(i);
					p = *my_free_list;
					// 如果存在空闲块,则移动start_free和end_free至这块空闲块的首尾地址,并再次调用chunk_alloc即可获得一块size大小的空闲块
					if (p != nullptr) {
						*my_free_list = p->free_list_link;
						start_free = (char*)p;
						end_free = start_free + i;
						return chunk_alloc(size, nobjs);
					}
				}
				end_free = 0;
				//这一步就是垂死挣扎,希望第一级配置器可以通过OOM机制尽力
				start_free = (char*)malloc_alloc::allocate(bytes_to_get);
			}
			// 增加heap_size,移动end_free指针
			heap_size += bytes_to_get;
			end_free = start_free + bytes_to_get;
			return chunk_alloc(size, nobjs);
		}
	};

	// 内存池空闲起始地址
	static char* start_free;
	// 内存池空闲末尾地址
	static char* end_free;
	// 内存池大小
	static size_t heap_size;
	// 16个空闲块列表表头的数组
	static obj* free_list[__NFREELISTS];
	// 已用内存大小
	static size_t m_use_size;
public:
	static T* allocate(size_t n) {
		obj** my_free_list;
		obj* result;
		// 增加使用内存
		m_use_size += n;
		// 比较申请内存的大小,若 n 大于 128,则直接使用一级分配器分配内存
		if (n > __MAX_BYTES) {
			return malloc_alloc<T>::allocate(n);
		}
		my_free_list = free_list + free_list_index(n);
		result = *my_free_list;
		if (result == nullptr) {
			T *res = refill(round_up(n));
			return res;
		}
		*my_free_list = result->free_list_link;
		return (T*)result;
	};
	static void deallocate(T* p, size_t n) {
		obj *q = (obj *)p;
		obj  ** my_free_list;

		m_use_size -= n;
		if (n > (size_t)__MAX_BYTES) {
			malloc_alloc::deallocate(p, n);
			return;
		}
		my_free_list = free_list + free_list_index(n);
		q->free_list_link = *my_free_list;
		*my_free_list = q;
	}
};
/*
	静态变量初始化
*/
template <class T>
char* default_alloc<T>::start_free = (char*)0;
template <class T>
char* default_alloc<T>::end_free = (char*)0;
template <class T>
size_t default_alloc<T>::heap_size = (size_t)0;
template <class T>
default_alloc<T>::obj* default_alloc<T>::free_list[__NFREELISTS] = { nullptr };
template <class T>
size_t default_alloc<T>::m_use_size = (size_t)0;

二级分配器allocators分配内存算法讲解如下: a、算法:allocate 输入:申请内存的大小size 输出:若分配成功,则返回一个内存的地址,否则返回NULL { if(size大于128){ 启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;} else { 将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_head; if(free_list_head不为空){ 从该列表中取下第一个空闲块并调整free_list; 返回free_list_head; } else { 调用refill算法建立空闲块列表并返回所需的内存地址; } } } b、算法: refill 输入:内存块的大小size 输出:建立空闲块链表并返回第一个可用的内存块地址 { 调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobj; if(块数为1)直接返回chunk; 否则 { 开始在chunk地址块中建立free_list; 根据size取free_list中对应的表头元素free_list_head; 将free_list_head指向chunk中偏移起始地址为size的地址处, 即free_list_head=(obj*)(chunk+size); 再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表; 返回chunk,即chunk中第一块空闲的内存块; } } c、算法:chunk_alloc 输入:内存块的大小size,预分配的内存块块数nobj(以引用传递) 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数 { 计算总共所需的内存大小total_bytes; if(内存池中足以分配,即end_free - start_free >= total_bytes) { 则更新start_free; 返回旧的start_free; } else if(内存池中不够分配nobj个内存块,但至少可以分配一个){ 计算可以分配的内存块数并修改nobj; 更新start_free并返回原来的start_free; } else { //内存池连一块内存块都分配不了 先将内存池的内存块链入到对应的free_list中后; 调用malloc操作重新分配内存池,大小为2倍的total_bytes加附加量,start_free指向返回的内存地址; if(分配不成功) { if(16个空闲列表中尚有空闲块) 尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size, nobj); else { 调用第一级分配器尝试out of memory机制是否还有用; } } 更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes; 调用chunk_alloc(size,nobj); } } d、算法:deallocate 输入:需要释放的内存块地址p和大小size { if(size大于128字节)直接调用free(p)释放; else{ 将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head; 调整free_list_head将p链入空闲列表块中; } }

list源码分析

GNU2.9版本

1)SGI STL中list是使用环状双向链表实现的。它的结点结构定义如下:

/*
	list_node节点设计了一个前驱prev和后继next,list内部实现一个双向环形链表
*/
template <class T>
struct __list_node {
	typedef void* void_pointer;
	void_pointer next;
	void_pointer prev;
	T data;
};

2)list的定义比较简单,而且环状双向链表的操作并不用过多的考虑边界条件。list创建的时候会创建一个空白的结点指针,并用其node成员指向它,下面是list的基本定义:

template <class T, class Alloc = alloc>
class list {
protected:
    typedef void* void_pointer;
    typedef __list_node<T> list_node;
    typedef simple_alloc<list_node, Alloc> list_node_allocator;
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef value_type& reference;
    typedef list_node* link_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
public:
    // 定义迭代器类型
    typedef __list_iterator<T, T&, T*> iterator;
protected:
    link_type node;  // 空白结点  链表尾结点
    // ... 一些基本操作
};

list环状双向链表示意图: 根据示意图我们很容易理解list的构造:

template <class T, class Alloc = alloc>
class list {
public:
// ...
    // 可以从图中直观的看出来
    iterator begin() { return (link_type)((*node).next); }
    iterator end() { return node; }

    // 默认构造函数
    list() { empty_initialize(); }
protected:
    // 为结点分配内存
    link_type get_node() { return list_node_allocator::allocate(); }
    // 回收内存
    void put_node(link_type p) { list_node_allocator::deallocate(p); }
    // 构造node
    link_type create_node(const T& x) {
        link_type p = get_node();
        construct(&p->data, x);
        return p;
    }
    // 销毁node
    void destroy_node(link_type p) {
        destroy(&p->data);
        put_node(p);
    }
    // 初始化
    void empty_initialize() {
        node = get_node();
        node->next = node;
        node->prev = node;
    }
};

list成员函数的实现其实就是对环状双向链表的操作。 首先是insert、erase、transfer的实现,关于插入删除大部分都调用这三个函数,实际上就是改变结点pre跟next指针的指向。

iterator insert(iterator position, const T& x) {
    link_type tmp = create_node(x);
    // 改变四个指针的指向 实际就是双向链表元素的插入
    tmp->next = position.node;
    tmp->prev = position.node->prev;
    (link_type(position.node->prev))->next = tmp;
    position.node->prev = tmp;
    return tmp;
}

iterator erase(iterator position) {
    // 改变四个指针的指向 实际就是双向链表的元素删除
    link_type next_node = link_type(position.node->next);
    link_type prev_node = link_type(position.node->prev);
    prev_node->next = next_node;
    next_node->prev = prev_node;
    destroy_node(position.node);
    return iterator(next_node);
}

// 将[first, last)插入到position位置(可以是同一个链表)
void transfer(iterator position, iterator first, iterator last) {
    if (position != last) {
        // 实际上也是改变双向链表结点指针的指向 具体操作看下图
        (*(link_type((*last.node).prev))).next = position.node;
        (*(link_type((*first.node).prev))).next = last.node;
        (*(link_type((*position.node).prev))).next = first.node;
        link_type tmp = link_type((*position.node).prev);
        (*position.node).prev = (*last.node).prev;
        (*last.node).prev = (*first.node).prev;
        (*first.node).prev = tmp;
    }
}

有了上面3个函数,list对外的接口实现就非常简单了:

void push_front(const T& x) { insert(begin(), x); }
void push_back(const T& x) { insert(end(), x); }
void pop_front() { erase(begin()); }
void pop_back() {
    iterator tmp = end();
    erase(--tmp);
}

// splice有很多重载版本
void splice(iterator position, list&, iterator first, iterator last) {
    if (first != last)
        transfer(position, first, last);
}

// merge函数实现跟归并排序中合并的操作类似
template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x) { ... }

// reserse函数每次都调用transfer将结点插入到begin()之前
template <class T, class Alloc>
void list<T, Alloc>::reverse() {
    if (node->next == node || link_type(node->next)->next == node) return;
    iterator first = begin();
    ++first;
    while (first != end()) {
        iterator old = first;
        ++first;
        transfer(begin(), old, first);
    }
}

// list必须使用自己的sort()成员函数 因为STL算法中的sort()只接受RamdonAccessIterator
// 该函数采用的是quick sort
template <class T, class Alloc>
void list<T, Alloc>::sort() { ... }