C++ 正确重用联合成员的存储并强制转换所涉及的指针(对象池)
我正试图编写一个符合C++20标准的对象池,它依赖于对象模型周围的新措辞,消除了一些未定义的行为。注释显示了我用于推理的标准草案的段落() 创建时,池为固定数量的对象分配存储,并在未使用的存储中管理自由列表。现在我假设类型C++ 正确重用联合成员的存储并强制转换所涉及的指针(对象池),c++,unions,lifetime,c++20,object-model,C++,Unions,Lifetime,C++20,Object Model,我正试图编写一个符合C++20标准的对象池,它依赖于对象模型周围的新措辞,消除了一些未定义的行为。注释显示了我用于推理的标准草案的段落() 创建时,池为固定数量的对象分配存储,并在未使用的存储中管理自由列表。现在我假设类型T没有常量或引用非静态成员 #include <iostream> #include <stdexcept> #include <type_traits> template <typename T> class ObjectPo
T
没有常量或引用非静态成员
#include <iostream>
#include <stdexcept>
#include <type_traits>
template <typename T>
class ObjectPool {
public:
using value_type = T;
ObjectPool(std::ptrdiff_t capacity) :
m_capacity(capacity),
m_nodes(
// Cast the result pointer back to Node* (https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13)
static_cast<Node*>(
/*
Implicitly creates (https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-10 and https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-13):
* the Node[capacity] array
* the Node union objects
* the Node* member subobjects
Returns a pointer to the array casted to void* (https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-11)
*/
operator new(capacity * sizeof(Node))
)
)
{
/*
The implicit creations happen because it makes the following code defined behaviour.
Otherwise it would be UB because:
* Pointer arithmetic without them pointing to elements of an Node[capacity] array (https://timsong-cpp.github.io/cppwp/n4861/expr.add#4)
* Accessing Node objects through 'pointer to object' pointers outside their lifetime (https://timsong-cpp.github.io/cppwp/n4861/basic.life#6.2).
* Accessing the Node* subobjects through 'pointer to object' pointers outside their lifetime.
*/
// Add all nodes to the freelist.
Node* next = nullptr;
for (std::ptrdiff_t i = m_capacity - 1; i >= 0; --i) {
m_nodes[i].next = next;
next = &m_nodes[i];
}
m_root = next;
}
~ObjectPool()
{
/*
Release the allocated storage.
This ends the lifetime of all objects (array, Node, Node*, T) (https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5).
*/
operator delete(m_nodes);
}
template <typename... Args>
T* create(Args&&... args)
{
// freelist is empty
if (!m_root) throw std::bad_alloc();
Node* new_root = m_root->next;
/*
Activate the 'storage' member (https://timsong-cpp.github.io/cppwp/n4861/class.union#7).
Is this strictly necessary?
*/
new(&m_root->storage) Storage;
/*
Create a T object in the storage of the std::aligned_storage object (https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-1).
This ends the lifetime of the std::aligned_storage object (https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5)?
Because std::aligned_storage is most likley implemented with a unsigned char[N] array (https://timsong-cpp.github.io/cppwp/n4861/meta.trans.other#1),
it 'provides storage' (https://timsong-cpp.github.io/cppwp/n4861/intro.object#3)
for the T object and so the T object is 'nested within' (https://timsong-cpp.github.io/cppwp/n4861/intro.object#4.2) the std::aligned_storage
which does not end its lifetime.
This means without knowing the implementation of std::aligned_storage I don't know if the lifetime has ended or not?
The union object is still in it's lifetime? The T object is 'nested within' the union object because it is
'nested within' the member subobject 'storage' because that 'provides storage' (https://timsong-cpp.github.io/cppwp/n4861/intro.object#4.3).
The union has no active members (https://timsong-cpp.github.io/cppwp/n4861/class.union#2).
*/
T* obj = new(&m_root->storage) T{std::forward<Args>(args)...};
m_root = new_root;
return obj;
}
void destroy(T* obj)
{
/* Destroy the T object, ending it's lifetime (https://timsong-cpp.github.io/cppwp/n4861/basic.life#5). */
obj->~T();
/* if std::aligned_storage is in its lifetime.
T represents the first byte of storage and is usable in limited ways (https://timsong-cpp.github.io/cppwp/n4861/basic.life#6).
The storage pointer points to the std::aligned_storage object (https://timsong-cpp.github.io/cppwp/n4861/expr.reinterpret.cast#7 and https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13).
I'm not sure is std::launder() is necessary here because we did not create a new object.
Storage* storage = reinterpret_cast<Node*>(storage);
*/
/* if std::aligned_storage is not in its lifetime.
Create a std::aligned_storage object in the storage of the former T object (https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-1).
This activates the 'storage' member of the corresponding union (https://timsong-cpp.github.io/cppwp/n4861/class.union#2).
*/
Storage* storage = new(obj) Storage;
/*
Get a pointer to the union from a pointer to a member (https://timsong-cpp.github.io/cppwp/n4861/basic.compound#4.2).
*/
Node* node = reinterpret_cast<Node*>(storage);
/*
Activate the 'next' member creating the corresponding subobject (https://timsong-cpp.github.io/cppwp/n4861/class.union#6),
the lifetime of the 'storage' subobject ends.
*/
node->next = m_root;
m_root = node;
}
std::ptrdiff_t capacity() const
{
return m_capacity;
}
private:
using Storage = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
union Node {
Node* next;
Storage storage;
};
std::ptrdiff_t m_capacity;
Node* m_nodes;
Node* m_root;
};
struct Block {
long a;
std::string b;
};
int main(int, char **)
{
ObjectPool<Block> pool(10);
Block* ptrs[10];
for (int i = 0; i < 10; ++i) {
ptrs[i] = pool.create(i, std::to_string(i*17));
}
std::cout << "Destroying objects\n";
for (int i = 0; i < 10; ++i) {
std::cout << ptrs[i]->a << " " << ptrs[i]->b << "\n";
pool.destroy(ptrs[i]);
}
return 0;
}
#包括
#包括
#包括
模板
类对象池{
公众:
使用值_type=T;
对象池(std::ptrdiff\t容量):
m_容量(容量),
m_节点(
//将结果指针强制转换回节点*(https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13)
静态浇铸(
/*
隐式创建(https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-10及https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-13):
*节点[容量]阵列
*节点联合对象
*节点*成员子对象
返回指向强制转换为void*的数组的指针(https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-11)
*/
新操作员(容量*sizeof(节点))
)
)
{
/*
隐式创建之所以发生,是因为它产生了以下代码定义的行为。
否则将是UB,因为:
*无需指向节点[capacity]数组元素的指针算法(https://timsong-cpp.github.io/cppwp/n4861/expr.add#4)
*通过生命周期之外的“指向对象的指针”指针访问节点对象(https://timsong-cpp.github.io/cppwp/n4861/basic.life#6.2).
*通过生命周期之外的“指向对象的指针”指针访问节点*子对象。
*/
//将所有节点添加到自由列表。
Node*next=nullptr;
对于(std::ptrdiff_t i=m_容量-1;i>=0;--i){
m_节点[i].next=next;
next=&m_节点[i];
}
m_根=下一个;
}
~ObjectPool()
{
/*
释放已分配的存储。
这将结束所有对象(数组、节点、节点*、T)的生存期(https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5).
*/
运算符删除(m_节点);
}
模板
T*create(Args&&…Args)
{
//自由列表为空
如果(!m_root)抛出std::bad_alloc();
节点*new_root=m_root->next;
/*
激活“存储”成员(https://timsong-cpp.github.io/cppwp/n4861/class.union#7).
这是绝对必要的吗?
*/
新建(&m_root->存储)存储;
/*
在std::aligned_存储对象的存储器中创建一个T对象(https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-1).
这将结束std::aligned_存储对象的生存期(https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5)?
因为std::aligned_存储最有可能使用无符号字符[N]数组实现(https://timsong-cpp.github.io/cppwp/n4861/meta.trans.other#1),
它“提供存储”(https://timsong-cpp.github.io/cppwp/n4861/intro.object#3)
对于T对象,因此T对象“嵌套在”(https://timsong-cpp.github.io/cppwp/n4861/intro.object#4.2)std::aligned\u存储
这并没有结束它的生命。
这意味着在不知道std::aligned_存储的实现的情况下,我不知道生命周期是否已经结束?
联合对象仍在其生存期内?T对象“嵌套在”联合对象内,因为它是
'嵌套在'成员子对象'存储'中,因为该'提供存储'(https://timsong-cpp.github.io/cppwp/n4861/intro.object#4.3).
工会没有活跃的成员(https://timsong-cpp.github.io/cppwp/n4861/class.union#2).
*/
T*obj=new(&m_root->storage)T{std::forward(args)…};
m_根=新的_根;
返回obj;
}
无效销毁(T*obj)
{
/*销毁T对象,结束其生命周期(https://timsong-cpp.github.io/cppwp/n4861/basic.life#5). */
obj->~T();
/*如果std::aligned_存储在其生存期内。
T表示存储的第一个字节,可用的方式有限(https://timsong-cpp.github.io/cppwp/n4861/basic.life#6).
存储指针指向std::aligned_存储对象(https://timsong-cpp.github.io/cppwp/n4861/expr.reinterpret.cast#7 及https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13).
我不确定std::launder()在这里是否必要,因为我们没有创建新对象。
存储*存储=重新解释(存储);
*/
/*如果std::aligned_存储不在其生存期内。
在前一个T对象的存储器中创建一个std::aligned_存储对象(https://timsong-cpp.github.io/cppwp/n4861/basic.memobj#intro.object-1).
这将激活相应联合的“存储”成员(https://timsong-cpp.github.io/cppwp/n4861/class.union#2).
*/
存储*存储=新(obj)存储;
/*
从指向成员的指针获取指向联合的指针(https://timsong-cpp.github.io/cppwp/n4861/basic.compound#4.2).
*/
节点*节点=重新解释(存储);
/*
激活创建相应子对象的“下一个”成员(https://timsong-cpp.github.io/cppwp/n4861/class.union#6),
“存储”子对象的生存期结束。
*/
节点->下一步=m_根;
m_根=节点;
}
std::ptrdiff_t容量()常数
{
返回m_容量;
}
私人:
使用Storage=typename std::aligned\u Storage::type;
联合节点{
节点*下一步;
#include <cstddef>
#include <new>
template <typename T>
class ObjectPool {
public:
using value_type = T;
ObjectPool(std::ptrdiff_t capacity)
: capacity_(capacity)
, nodes_(new Node[capacity])
{
// Add all nodes to the freelist.
Node* next = nullptr;
for (std::ptrdiff_t i = capacity_ - 1; i >= 0; --i) {
nodes_[i].next = next;
next = &nodes_[i];
}
root_ = next;
}
~ObjectPool()
{
delete[] nodes_;
}
template <typename... Args>
T* create(Args&&... args)
{
// freelist is empty
if (!root_) throw std::bad_alloc();
auto *allocate = root_;
root_ = root_->next;
new(&allocate->storage) decltype(allocate->storage);
//Note: not exception-safe.
T* obj = new(&allocate->storage) T(std::forward<Args>(args)...);
return obj;
}
void destroy(T* obj)
{
obj->~T();
Node *free = std::launder(reinterpret_cast<Node*>(obj));
free->next = root_;
root_ = free;
}
std::ptrdiff_t capacity() const
{
return capacity_;
}
private:
union Node
{
Node* next;
alignas(T) std::byte storage[sizeof(T)];
};
std::ptrdiff_t capacity_;
Node* nodes_;
Node* root_ = nullptr;
};