1. FOR_EACH 宏实现

代码目录:cyber/base/for_each.h

DEFINE_TYPE_TRAIT(HasLess, operator<)  // NOLINT

template <class Value, class End>
typename std::enable_if<HasLess<Value>::value && HasLess<End>::value,
bool>::type
LessThan(const Value& val, const End& end) {
return val < end;
}

template <class Value, class End>
typename std::enable_if<!HasLess<Value>::value || !HasLess<End>::value,
bool>::type
LessThan(const Value& val, const End& end) {
return val != end;
}


#define FOR_EACH(i, begin, end) \
for (auto i = (true ? (begin) : (end)); \
apollo::cyber::base::LessThan(i, (end)); ++i)

我们先看看这个宏的用法:

std::vector<int> vec;
FOR_EACH(i, 0, 100) { vec.push_back(i); }
FOR_EACH(it, vec.begin(), vec.end()) { std::cout<<*it<<std::endl; }

简单来说就是用于对不同类型的数据进行遍历操作:

DEFINE_TYPE_TRAIT(HasLess, operator<) 这个trait操作是保证传进来的beginend类型重载了<

for循环里会去调用LessThan函数,LessThan函数的两个模板参数就是begin所代表的类型和End所代表的类型。

HasLess<Value>::value && HasLess<End>::value满足时,才会走第一个模板函数,将会对valend的值进行比较,函数返回值为bool类型,

Value或者End其中任意一个类型不具有<的重载则会走第二个模板函数,函数内部会对这两个值进行判断是否是相等

如果用户在使用时提供的类型既不支持 < 运算符,也不支持 != 运算符,那么在编译时将会导致错误。

2. 对象池的实现

源码目录:cyber/base/object_pool.h

template <typename T>
class ObjectPool : public std::enable_shared_from_this<ObjectPool<T>> {
public:
using InitFunc = std::function<void(T *)>;
using ObjectPoolPtr = std::shared_ptr<ObjectPool<T>>;

template <typename... Args>
explicit ObjectPool(uint32_t num_objects, Args &&... args);

template <typename... Args>
ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args);

virtual ~ObjectPool();

/* 拿到一个对象*/
std::shared_ptr<T> GetObject();

private:
struct Node {
T object;
Node *next;
};
/*禁用拷贝构造*/
ObjectPool(ObjectPool &) = delete;
ObjectPool &operator=(ObjectPool &) = delete;
void ReleaseObject(T *);

uint32_t num_objects_ = 0;
char *object_arena_ = nullptr;
Node *free_head_ = nullptr;
};


template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
f(obj);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

template <typename T>
ObjectPool<T>::~ObjectPool() {
if (object_arena_ != nullptr) {
const size_t size = sizeof(Node);
FOR_EACH(i, 0, num_objects_) {
reinterpret_cast<Node *>(object_arena_ + i * size)->object.~T();
}
std::free(object_arena_);
}
}

template <typename T>
void ObjectPool<T>::ReleaseObject(T *object) {
if (cyber_unlikely(object == nullptr)) {
return;
}

reinterpret_cast<Node *>(object)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(object);
}

template <typename T>
std::shared_ptr<T> ObjectPool<T>::GetObject() {
if (cyber_unlikely(free_head_ == nullptr)) {
return nullptr;
}

auto self = this->shared_from_this();
auto obj =
std::shared_ptr<T>(reinterpret_cast<T *>(free_head_),
[self](T *object) { self->ReleaseObject(object); });
free_head_ = free_head_->next;
return obj;
}
template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
f(obj);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

template <typename T>
ObjectPool<T>::~ObjectPool() {
if (object_arena_ != nullptr) {
const size_t size = sizeof(Node);
FOR_EACH(i, 0, num_objects_) {
reinterpret_cast<Node *>(object_arena_ + i * size)->object.~T();
}
std::free(object_arena_);
}
}

template <typename T>
void ObjectPool<T>::ReleaseObject(T *object) {
if (cyber_unlikely(object == nullptr)) {
return;
}

reinterpret_cast<Node *>(object)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(object);
}

template <typename T>
std::shared_ptr<T> ObjectPool<T>::GetObject() {
if (cyber_unlikely(free_head_ == nullptr)) {
return nullptr;
}

auto self = this->shared_from_this();
auto obj =
std::shared_ptr<T>(reinterpret_cast<T *>(free_head_),
[self](T *object) { self->ReleaseObject(object); });
free_head_ = free_head_->next;
return obj;
}

首先来看构造函数:

template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

向对象池中传递目标的类型和构建目标的参数,用T ... Args来进行传递,在构造函数内部首先计算了一个Node的大小,然后分配num_objectsnode大小的内存作为目标池来管理,根据这个node的定义我们可以知道对象池管理对象时是以链表的方式将各个链表链接起来的,再分配完毕内存后,通过FOR_EACH来在指定的地址处创建对象,然后让创建的这个对象的指针指向free_head_

还有另外一个构造函数:

template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
f(obj);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

这个构造和上一个的区别在于多了一个参数InitFunc f,允许在构造对象池的时候提供一个操作对象的函数,

using InitFunc = std::function<void(T *)>;

InitFunc是一个可调用对象,此对象的返回值是void,需要的参数是T *类型,所以在构造对象池的时候使用f(obj);来操作了对象

image-20231202110144511

接着来看看释放一个对象的操作,释放对象就是将一个对象重新放入对象池中:然后更新free_head的值

template <typename T>
void ObjectPool<T>::ReleaseObject(T *object) {
if (cyber_unlikely(object == nullptr)) {
return;
}

reinterpret_cast<Node *>(object)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(object);
}

image-20231202111140335

最后是拿到一个对象的操作:

template <typename T>
std::shared_ptr<T> ObjectPool<T>::GetObject() {
if (cyber_unlikely(free_head_ == nullptr)) {
return nullptr;
}

auto self = this->shared_from_this();
auto obj =
std::shared_ptr<T>(reinterpret_cast<T *>(free_head_),
[self](T *object) { self->ReleaseObject(object); });
free_head_ = free_head_->next;
return obj;
}

通过std::shared_ptr<T> ptr(pointer, deleter);来得到这个obj,第二个参数即是智能指针管理的这个对象的引用计数归零时将要执行的操作,这里就是说当obj的引用计数归零时,让此对象重新返回对象池,所以传入的可调用对象的形式是一个lamada函数: [self](T *object) { self->ReleaseObject(object); },然后更新free_head_

所以从对象池中拿对象从arena的高地址处free_head_开始拿,释放也是如此,将一个对象插入到free_head_之后,然后更新free_head_

最后是析构函数:

template <typename T>
ObjectPool<T>::~ObjectPool() {
if (object_arena_ != nullptr) {
const size_t size = sizeof(Node);
FOR_EACH(i, 0, num_objects_) {
reinterpret_cast<Node *>(object_arena_ + i * size)->object.~T();
}
std::free(object_arena_);
}
}

遍历调用对象的自己析构函数,然后释放arena这块内存

参考链接