std::allocator
类模板定义在<memory>
头文件中,是C++标准库容器默认的内存分配器实现,它封装了包括访问与寻址,分配与释放和对象构建与析构的策略。std::allocator
是无状态的,也就是说,所有的allocator实例都是可互换的,可比较且相等的,甚至可以用一个allocator实例释放另一个allocator实例分配的内存。
/* 代码示例 */
std::allocator<int> a1;
std::allocator<int> a2;
int *p = a1.allocate(1); // 分配一个int所需要的空间
a2.deallocate(p, 1); // 释放由a1分配的空间
std::allocator
类模板的典型用法如下,
/* 代码示例 */
std::allocator<std::string> a;
std::string* s = a.allocate(1); // 分配一个std::string所需要的空间
a.construct(s, "foo"); // 在之前分配的空间中构造一个std::string,其初始值为foo
a.destroy(s); // 析构之前构造的std::string,但空间并未释放
a.deallocate(s, 1); // 释放最初分配的空间
源码分析
以下源码分析基于gcc 9.2.0和LLVM clang 9.0.1。
类模板定义
/* gcc */
template<typename _Tp>
class allocator : public __allocator_base<_Tp>;
template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;
template<typename _Tp>
class new_allocator;
/* clang */
template <class _Tp>
class allocator;
可以看出,clang的定义要更简单一些。
gcc的std::allocator
是继承自__gnu_cxx::new_allocator
类模板的,后者是一个精确匹配C++标准allocator的实现,也就是分配内存使用全局new
操作符,释放内存使用全局delete
操作符。gcc的allocator成员函数实现都在__gnu_cxx::new_allocator
中。
成员类型定义
std::allocator
定义了一些成员类型,其中有一个特殊的rebind
类型,它用来将一个std::allocator
类型转换成另外一个std::allocator
类型。
std::allocator<int> a1;
decltype(a1)::rebind<char>::other a2; // 相当于std::allocator<char> a2
std::allocator<int>::rebind<char>::other a3; // 相当于std::allocator<char> a3
rebind
类型的实现也非常简单,只包含如下的类型定义。
template <class _Up> struct rebind {typedef allocator<_Up> other;};
值得注意的是,rebind
类型在C++17中被废弃,将会在C++20中被移除。
成员变量
std::allocator
的实现没有成员变量,这也说明了它是无状态的。
构造/析构函数
std::allocator
定义的多个构造函数,包括拷贝构造函数,以及析构函数,它们的实现都是空函数,不做任何操作。
成员函数
address成员函数
pointer std::allocator<T>::address( reference x ) const;
用于获取引用x的内存地址,即使在地址运算符&被重载的情况下也可以正常工作。gcc和clang的实现基本相同,代码如下:
pointer address(reference __x) const {
return std::__addressof(__x);
}
template<typename _Tp>
inline _Tp* __addressof(_Tp& __r) {
return __builtin_addressof(__r);
}
__builtin_addressof
是一个内置函数,用来执行内置地址操作符&的取地址功能,忽略任何地址操作符&重载。clang实现中,存在__builtin_addressof
内置函数时使用此内置函数获取地址,不存在此内置函数时,使用如下代码达到同样目的。
template <class _Tp>
inline _Tp* addressof(_Tp& __x) {
return reinterpret_cast<_Tp *>(const_cast<char *>(&reinterpret_cast<const volatile char &>(__x)));
}
注意此函数在C++17中被废弃,将会在C++20中被移除。
max_size成员函数
size_type std::allocator<T>::max_size() const;
此函数返回最大的可分配数量,它的实现很简单,
size_type max_size() const {
return size_type(~0) / sizeof(_Tp);
}
注意此函数在C++17中被废弃,将会在C++20中被移除。
allocate成员函数
pointer std::allocator<T>::allocate( size_type n, const void * hint = 0 );
用来分配n*sizeof(T)
字节的未初始化内存空间,其中hint
参数的本意是使allocate
分配尽量靠近hint
指针的内存,但是在实现中并没有使用,并且在C++17中增加了一个没有hint
参数的allocate
成员函数。
gcc和clang的实现大同小异,都是使用全局new
操作符分配n*sizeof(T)
字节的内存空间。不同的是,当检测到n过大时(n>this->max_size()
),gcc抛出bad_alloc
异常,而clang抛出length_error
异常。
/* gcc */
pointer allocate(size_type __n, const void* = static_cast<const void*>(0)) {
if (__n > this->max_size())
std::__throw_bad_alloc();
return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
}
/* clang */
pointer allocate(size_type __n, allocator<void>::const_pointer = 0) {
if (__n > max_size())
__throw_length_error("allocator<T>::allocate(size_t n)"
" 'n' exceeds maximum supported size");
return static_cast<pointer>(::operator new(__n * sizeof(_Tp)));
}
deallocate成员函数
void deallocate(pointer __p, size_type __n);
用来释放__p
所指向的allocate
分配的内存空间,参数__n
指之前分配的数量,此参数在gcc中并没有使用,在clang中根据编译条件的不同来决定使用与否。
/* gcc */
void deallocate(pointer __p, size_type) {
::operator delete(__p);
}
/* clang */
void deallocate(pointer __p, size_type __n) {
#ifdef _LIBCPP_HAS_NO_SIZED_DEALLOCATION
return ::operator delete(__ptr);
#else
return ::operator delete(__ptr, __n*sizeof(T)); // 具有内存大小参数的delete操作符
#endif
}
construct成员函数
template<typename _Up, typename... _Args>
void construct(_Up* __p, _Args&&... __args);
用来在__p
所指向的内存中构建一个_Up
类型的对象,并将__args
参数列表传给_Up
类型的构造函数。gcc和clang的实现相同,都是使用放置式new
在制定内存中构建对象,并使用std::forward
转发模板参数到构造函数。
template<typename _Up, typename... _Args>
void construct(_Up* __p, _Args&&... __args) {
::new((void *)__p) _Up(std::forward<_Args>(__args)...);
}
注意此函数在C++17中被废弃,将会在C++20中被移除。
destroy成员函数
template<typename _Up>
void destroy(_Up* __p);
用于析构__p
所指向对象但并不释放内存。
此函数gcc和clang实现相同,都是直接调用析构函数。
template<typename _Up>
void destroy(_Up* __p) {
__p->~_Up();
}
注意此函数在C++17中被废弃,将会在C++20中被移除。
模板特化
除了通用的allocator
模板,gcc和clang都实现了某些类型的模板特化,包括allocator<void>
,allocator<const _Tp>
等。
其中allocator<const _Tp>
偏特化模板在gcc和clang中实现完全不同,
/* gcc */
template<typename _Tp>
class allocator<const _Tp> {
public:
typedef _Tp value_type;
template<typename _Up> allocator(const allocator<_Up>&) { }
};
gcc中的allocator<const _Tp>
偏特化完全是一个无效的allocator
类型,分配释放等成员函数都没有定义。而clang中的allocator<const _Tp>
偏特化模板和通用allocator<_Tp>
模板一样,拥有全部的成员函数。