空间配置器
从STL使用的角度,我们一般不需要了解空间配置器(allocator);但是从STL实现的角度,空间配置器就很重要了
allocator为什么叫做空间配置器,不叫内存配置器?因为allocator的实现不仅可以从内存分配空间,也可以从磁盘分配空间
在STL容器的实现中,每个容器都有一个模板参数,可以指定使用的空间配置器
allocator包含以下类型
- value_type:T
- pointer (C++17 中弃用)(C++20 中移除):T*
- const_pointer (C++17 中弃用)(C++20 中移除):const T*
- reference (C++17 中弃用)(C++20 中移除):T&
- const_reference (C++17 中弃用)(C++20 中移除):const T&
- size_type:std::size_t
- difference_type:std::ptrdiff_t
allocator主要包含以下成员函数
- allocate:分配未初始化的存储
- deallocate:解分配存储
- construct(C++17 中弃用)(C++20 中移除):在分配的存储构造对象
- destroy (C++17 中弃用)(C++20 中移除):析构在已分配存储中的对象
在C++的头文件<memory>
中定义了一些与未初始化内存相关的函数,在STL容器的实现中,起到了很大的作用,如construct_at(C++20),uninitialized_copy,uninitialized_fill等
空间配置器在STL容器中的应用
STL容器的实现思想中,实际上将空间的分配/释放和对象的构造/析构进行了拆分:空间配置器负责空间的分配/释放;<memory>
中的未初始化内存相关的函数负责对象构造/析构,移动等
能够实现上述拆分,主要由于C++的以下特性
- operator new只进行内存分配,不进行对象构造
- placement new是operator new的一个特殊版本,在已分配的内存上构造对象
- new关键字的作用是:调用operator new,并调用对象的构造函数
以下两种代码实际上是等价的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <cstdio>
#include <string> #include <vector>
class Test { public: Test(const std::string& name): name_(name) { printf("construct %s\n", name_.c_str()); } ~Test() { printf("destroy %s\n", name_.c_str()); }
private: std::string name_; };
int main(int argc, char* argv[]) { { auto* t = new Test("new_test"); delete t; } { void* p = ::operator new(sizeof(Test)); auto* t = reinterpret_cast<Test*>(p); t->Test::Test("allocator_test"); t->~Test(); ::operator delete(p); } return 0; }
|
未初始化内存算法的实现
有了上述基础,可以实现简单的未初始化内存的算法,更多实现参见gtl_memory.h
construct_at用到了std::forward的完美转发(C++11),两次强制转换是为了除去cv限定符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
template <typename T, typename... Args> T* construct_at(T* p, Args&&... args) { ::new (const_cast<void*>(static_cast<const volatile void*>(p))) T(std::forward<Args>(args)...); return p; }
template <typename T> void destroy_at(T* p) { if constexpr (std::is_array_v<T>) { for (auto& v : *p) { gtl::destroy_at(gtl::addressof(v)); } } else { p->~T(); } }
template <typename InputIt, typename ForwardIt> ForwardIt uninitialized_copy_range(InputIt first, InputIt last, ForwardIt d_first, std::input_iterator_tag, std::forward_iterator_tag) { for (; first != last; ++first, ++d_first) { gtl::construct_at(gtl::addressof(*d_first), *first); } return d_first; }
template <typename InputIt, typename SizeType, typename ForwardIt> ForwardIt uninitialized_copy_range_n(InputIt first, SizeType count, ForwardIt d_first, std::input_iterator_tag, std::forward_iterator_tag) { for (; count > 0; ++first, ++d_first, --count) { gtl::construct_at(gtl::addressof(*d_first), *first); } return d_first; }
template <typename InputIt, typename ForwardIt> ForwardIt uninitialized_copy(InputIt first, InputIt last, ForwardIt d_first) { using input_iterator_category = typename std::iterator_traits<InputIt>::iterator_category; using forward_iterator_category = typename std::iterator_traits<ForwardIt>::iterator_category; return gtl::uninitialized_copy_range(first, last, d_first, input_iterator_category(), forward_iterator_category()); }
|
参考
Last updated: