sizeof和类型,保证

3

我找不到证据来证明/反驳以下代码片段没有设计缺陷,就正确性而言。

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有关的所有内容,这里基本上将其用作具有与提供类型相同大小的内存块。

我不想在m_memoryBlock中明确创建Item对象,因此我需要一种“具有足够空间的内存块”。

我能否确定Chunk将与提供的类型具有相同的大小?请提供一些假设不起作用的示例。

如果我的假设完全错误,我该如何处理?


这是完全有效的方法(尽管对齐可能会有所不同)。Chunk保证至少与Item的大小相同(可能更大)。 - SergeyA
你是在问 sizeof(Chunk) == sizeof(Item) 是否成立吗? - NathanOliver
1
为什么不使用std::aligned_storage代替Chunk - Cornstalks
@NathanOliver,是的,它对于所有不同的Item都有效吗?例如,如果我有一个派生类,那怎么办? - varnie
@Cornstalks 在我的设计中,我只执行加法操作,没有删除操作。 - varnie
显示剩余4条评论
1个回答

4
在这些设计中,内存必须适合于您想要在其中创建的对象类型。标准内置类型通常具有“自然”对齐方式,其等于它们的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惯用语法的优化以避免内存分配。但是,这需要在头文件中公开实现的定义以获取其大小和对齐方式,从而破坏了Pimpl的目的。有关详细信息,请参见快速Pimpl惯用语法


另一个细节是,如果要复制/移动Chunk并希望存储的对象保持有效,则这些存储的对象必须是平凡可复制的,例如:

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>不是一个好的选择,因为当向量增长并重新分配时,所有指向池中存储对象的指针和引用都会失效。

在通用内存池中,对象不应该移动。


谢谢你这么详细的回答!你能再解释一下这个句子吗:"在某些情况下可能会不方便或不可取"? - varnie
顺便问一下,它不应该是 typedef std::aligned_storage<sizeof(Item), alignof(Item)>::type Chunk 吗? - varnie
嗯...如果我使用非平凡可复制的类,但仍想使用std::aligned_storage方法,现在我有点困惑。 - varnie
1
@varnie:可能是可行的,但是很危险。你不能依赖于向量的增长和重定位。任何导致向量增长超过capacity的操作都需要您创建一个所需大小且具有足够额外容量的新向量,调用Item的移动构造函数(或者如果失败,则为复制构造函数后跟dtor),交换两个向量,然后在刚刚交换出来的向量中调用所有Item的dtor。如果这听起来不合理,那么您可能还不了解C++的设计容器。因为我甚至还没有提到异常。 - MSalters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接