sizeof 和类型,保证
sizeof and types, guarantees
我找不到proof/disproof下面的代码片段没有设计缺陷,关于正确性。
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;
};
我关心Chunk 的所有内容,Chunk 在这里本质上用作与提供的类型具有相同大小的内存包。
我不想在 m_memoryBlock
中显式创建 Item 对象,因此我需要某种 "chunk of memory having enough of space".
我可以确定块的大小与提供的类型相同吗?请提供一些此假设不适用的示例。
如果我的假设完全错误,我该如何处理?
在此类设计中,内存必须与您要在其中创建的对象类型适当对齐。
标准内置类型通常具有自然对齐方式,等于它们的sizeof
,即sizeof(T) == alignof(T)
。
char
数组的对齐是1个字节,这对其他任何东西来说都是不够的。
强制对齐的一种简单方法是像这样使用 std::max_align_t
:
union Chunk
{
std::max_align_t max_align;
char mem[sizeof(Item)];
};
这将使 Chunk::mem
适合任何标准内置类型。
另一种方法是使用 C++11 alignas
关键字精确对齐要放置在 char
数组中的类型:
struct Chunk
{
alignas(Item) char mem[sizeof(Item)];
};
这正是 std::aligned_storage
为您所做的。
但是,这需要公开 Item
的定义,这在某些情况下可能不方便或不受欢迎。
例如,此方法可用作 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 的优化以获取更多详细信息。
另一个细节是,如果您想 copy/move Chunk
s 并希望存储的对象保持有效,则这些存储的对象必须是 trivially copyable,例如
static_assert(std::is_trivially_copyable<Item>::value,
"Item cannot be copied byte-wise");
typedef std::aligned_storage<sizeof(Item), alignof(Item)> Chunk;
对于内存池 std::vector<Chunk>
不是一个好的选择,因为在向量增长时重新分配时,所有指向存储在池中的对象的指针和引用都会失效。
对象不应在一般内存池中移动。
我找不到proof/disproof下面的代码片段没有设计缺陷,关于正确性。
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;
};
我关心Chunk 的所有内容,Chunk 在这里本质上用作与提供的类型具有相同大小的内存包。
我不想在 m_memoryBlock
中显式创建 Item 对象,因此我需要某种 "chunk of memory having enough of space".
我可以确定块的大小与提供的类型相同吗?请提供一些此假设不适用的示例。
如果我的假设完全错误,我该如何处理?
在此类设计中,内存必须与您要在其中创建的对象类型适当对齐。
标准内置类型通常具有自然对齐方式,等于它们的sizeof
,即sizeof(T) == alignof(T)
。
char
数组的对齐是1个字节,这对其他任何东西来说都是不够的。
强制对齐的一种简单方法是像这样使用 std::max_align_t
:
union Chunk
{
std::max_align_t max_align;
char mem[sizeof(Item)];
};
这将使 Chunk::mem
适合任何标准内置类型。
另一种方法是使用 C++11 alignas
关键字精确对齐要放置在 char
数组中的类型:
struct Chunk
{
alignas(Item) char mem[sizeof(Item)];
};
这正是 std::aligned_storage
为您所做的。
但是,这需要公开 Item
的定义,这在某些情况下可能不方便或不受欢迎。
例如,此方法可用作 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 的优化以获取更多详细信息。
另一个细节是,如果您想 copy/move Chunk
s 并希望存储的对象保持有效,则这些存储的对象必须是 trivially copyable,例如
static_assert(std::is_trivially_copyable<Item>::value,
"Item cannot be copied byte-wise");
typedef std::aligned_storage<sizeof(Item), alignof(Item)> Chunk;
对于内存池 std::vector<Chunk>
不是一个好的选择,因为在向量增长时重新分配时,所有指向存储在池中的对象的指针和引用都会失效。
对象不应在一般内存池中移动。