C++ STL中的allocator
绪论
As we all kown,C++中的STL容器使用其内置的std::allocator
分配内存。如果我们想要更改STL分配内存的行为,我们不用更改容器的逻辑,只需要更改传入的allocator
即可。一直以来,allocator
对我来讲都笼罩着一层迷雾,总感觉是一个很复杂很恐怖的东西,最近因为课程原因,需要使用平台特定的内存管理函数(在NVM仿真平台quartz中需要使用其提供的pmalloc和pfree函数管理非易失性内存),就研究了一下std::allocator
是怎么实现的。但是认真了解以后发现没有什么神奇的东西。
源码剖析
allocator
主要有四个接口:
T *allocate(size_t)
:用void * ::operator new(size_t)
分配内存(但是不构造,其实就是对malloc
的封装)void deallocate(T*, size_t)
:使用void ::operator delete(void *)
释放内存(其实是对free
的封装)void construct(T*, Args&&)
:使用定位new(placement new::new(void*) T(std::forward<Args>(args)...)
)在指定内存上进行构造void destroy(T*)
:调用析构函数(p->~T()
)进行析构
不过后两者在C++17中被废弃了,在C++20中被删除了,我在stackoverflow上看到一个回答的大概意思是以后应该用std::allocator_traits::construct
了。原回答:Why are are std::allocator’s construct and destroy functions deprecated in c++17?
去看了一下STL 的源码,发现std::allocator
继承了__allocator_base
,然后后者又继承了__gnu_cxx::new_allocator
,也就是说其实用的是GNU版本的allocator
,我认真阅读了一下__gnu_cxx::new_allocator
的实现,发现的确挺简洁,没有什么特殊的东西,通过把里面的宏改成普通的关键字,删除掉我觉得没有什么用的版本判断,就可以得到一个简单的allocator
实现:
// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/10/25
// Description: __gnu__cxx::new_allocator
#ifndef TEMP_PALLOCATOR_H
#define TEMP_PALLOCATOR_H
#include "utils.h"
#include <new>
#include <type_traits>
namespace edward
{
using std::size_t;
using std::ptrdiff_t;
/**
* @brief An allocator that uses global new, as per [20.4].
* @ingroup allocators
*
* This is precisely the allocator defined in the C++ Standard.
* - all allocation calls operator new
* - all deallocation calls operator delete
*
* @tparam T Type of allocated object.
*/
template<typename T>
class pallocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
/*
* 在阅读《Modern Effective C++》后发现原文件中的rebind只不过是一个类型别名,可以用模板using代替,
* 但是在尝试修改sgi-stl的源码的过程中我发现他们的实现依赖于这个rebind,所以还是就用rebind吧
template<typename T1>
using other = pallocator<T1>;
*/
template<typename Tp1>
struct rebind
{ typedef pallocator<Tp1> other; };
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2103. propagate_on_container_move_assignment
typedef std::true_type propagate_on_container_move_assignment;
constexpr pallocator() noexcept { }
constexpr
pallocator(const pallocator&) noexcept { }
template<typename T1>
constexpr
pallocator(const pallocator<T1>&) noexcept { }
~pallocator() noexcept { }
pointer
address(reference __x) const noexcept
{ return std::__addressof(__x); }
const_pointer
address(const_reference __x) const noexcept
{ return std::__addressof(__x); }
// NB: __n is permitted to be 0. The C++ standard says nothing
// about what the return value is when __n == 0.
[[nodiscard]] pointer //cannot discard return value!!!
allocate(size_type __n, const void* = static_cast<const void*>(0))
{
if (__n > this->max_size())
throw std::bad_alloc();
print("allocate", __n * sizeof(T), "bytes");
return static_cast<T*>(::operator new(__n * sizeof(T)));
}
// __p is not permitted to be a null pointer.
void
deallocate(pointer __p, size_type __n)
{
print("deallocate", __n * sizeof(T), "bytes");
::operator delete(__p);
}
size_type
max_size() const noexcept
{
return size_t(__PTRDIFF_MAX__) / sizeof(T);
}
template<typename _Up, typename... _Args>
void
construct(_Up* __p, _Args&&... __args)
noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
template<typename _Up>
void
destroy(_Up* __p)
noexcept(std::is_nothrow_destructible<_Up>::value)
{ __p->~_Up(); }
template<typename _Up>
friend bool
operator==(const pallocator&, const pallocator<_Up>&) noexcept
{ return true; }
template<typename _Up>
friend bool
operator!=(const pallocator&, const pallocator<_Up>&) noexcept
{ return false; }
};
} // namespace
#endif //TEMP_PALLOCATOR_H
我在allocate
和deallocate
函数中添加了打印函数,utils.h
头文件的获取可以看一下我之前的博客:C++ 工具函数库
后来我尝试修改sgi-stl的源码,想要用自己修改的这个allocator,但是发现有一些问题:
- sgi-stl依赖于
typename rebind::other
重命名 - sgi-stl中的
allocator
的方法都是静态的!!!我调试了好久才发现这个问题,虽然sgi-stl已经算是比较容易阅读的版本了,但是还是有很多的typedef,然后里面还有自己的_Alloc_traits
,把我看吐了。
参考资料
- 第10篇:C++ 堆内存管理器-allocator
- 《C++ primer》第5版 12.2.2节 allocator类