C++ 正确重用联合成员的存储并强制转换所涉及的指针(对象池)

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

我正试图编写一个符合C++20标准的对象池,它依赖于对象模型周围的新措辞,消除了一些未定义的行为。注释显示了我用于推理的标准草案的段落()

创建时,池为固定数量的对象分配存储,并在未使用的存储中管理自由列表。现在我假设类型
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;
};