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?
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
chararray is 1 byte, which is insufficient for anything else.One simple way to enforce alignment is to use
std::max_align_tlike this:That will make
Chunk::memsuitably 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
chararray using C++11alignaskeyword:And this is exactly what
std::aligned_storagedoes 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.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.