Search code examples
c++sizeofalignof

sizeof and types, guarantees


I cannot find a proof/disproof that the following code snippet has no design flaws, speaking about the correctness.

template <class Item>
class MyDirtyPool {
public:
    template<typename ... Args>
    std::size_t add(Args &&... args) {

        if (!m_deletedComponentsIndexes.empty()) {
            //reuse old block
            size_t positionIndex = m_deletedComponentsIndexes.back();
            m_deletedComponentsIndexes.pop_back();
            void *block = static_cast<void *> (&m_memoryBlock[positionIndex]);
            new(block) Item(std::forward<Args>(args) ...);

            return positionIndex;
        } else {
            //not found, add new block
            m_memoryBlock.emplace_back();

            void *block = static_cast<void *> (&m_memoryBlock.back());
            new(block) Item(std::forward<Args>(args) ...);

            return m_memoryBlock.size() - 1;
        }
    }
    //...all the other methods omitted
private:
    struct Chunk {
        char mem[sizeof(Item)]; //is this sane?
    };

    std::vector<Chunk> m_memoryBlock; //and this one too, safe?
    std::deque<std::size_t> m_deletedComponentsIndexes;
};

I am concerned about all the stuff with Chunk, which is used here in essence as a bag of memory having the same size as the supplied type. I don't want to explicitly create Item objects in the m_memoryBlock, therefore I need some kind of "chunk of memory having enough of space".

Can I be sure that Chunk will have the same size as the supplied type? Please provide some examples where this assumption won't work.

If I am totally wrong in my assumptions, how should I deal with it?


Solution

  • In such designs the memory must be suitably aligned for the type of objects you would like to create there.

    Standard built-in types normally have natural alignment, which equals to their sizeof, i.e. sizeof(T) == alignof(T).

    The alignment of char array is 1 byte, which is insufficient for anything else.

    One simple way to enforce alignment is to use std::max_align_t like this:

    union Chunk 
    {
        std::max_align_t max_align;
        char mem[sizeof(Item)]; 
    };
    

    That will make Chunk::mem suitably aligned for any standard built-in type.


    Another way is to use the exact alignment of the type you would like to place in that char array using C++11 alignas keyword:

    struct Chunk 
    {
        alignas(Item) char mem[sizeof(Item)]; 
    };
    

    And this is exactly what std::aligned_storage does for you.

    However, that would require exposing the definition of Item, which may be inconvenient or undesirable in some cases.

    For example, this method could be used as an optimization for Pimpl idiom to avoid a memory allocation, however, that would require exposing the definition of the implementation in the header file to get its size and alignment, therefore defeating the purpose of Pimpl. See The Fast Pimpl Idiom for more details.


    Another detail, is that if you would like to copy/move Chunks and expect the stored objects to remain valid, those stored objects must be trivially copyable, e.g.

    static_assert(std::is_trivially_copyable<Item>::value, 
                  "Item cannot be copied byte-wise");
    typedef std::aligned_storage<sizeof(Item), alignof(Item)> Chunk;
    

    For a memory pool std::vector<Chunk> is not a good choice because on reallocation when the vector grows all pointers and references to the objects stored in the pool get invalidated.

    The objects should not move in a general memory pool.