在 C++ 中动态大小 类

dynamically sized classes in c++

我想创建一个 class Word,其中包含一个词。我将为字典中的几乎每个单词(如此多)创建一个 class 的实例——不,我不能使用树结构来为我的特定应用程序存储它。当然,字符串的大小可能会有所不同,我不想破坏内存。我想在 class 中这样做:

class Word {
    public:
        ...
    private:
        int         len;
        LetterData  letters[];
};

然后动态分配 Word 使用:

Word *pNewWord = malloc(sizeof(Word)+sizeof(LetterData)*len);   

我意识到这不是很 C++ 风格。所以我的问题是:首先,是否有更好的方法来做到这一点,如果没有,这会导致问题吗? Word 不会继承任何其他 class 类型(我很确定继承会杀死它......)。

注意:内存使用和速度非常重要——我想避免每个词有一个额外的指针,我想避免每次访问有一个额外的指针延迟...

在编译时,编译器必须知道 class 的大小,所以无论 class 包含什么,编译器都需要知道 [=] 的每个成员的大小25=].

但是请注意,成员letters是一个指针,它的大小是已知的,所以即使它指向一个可以改变它大小的内存,它只需要分配space 来保存那个指针。

如果你想使用malloc来分配内存,我在C++中不推荐,给它class的大小就足够了:

Word *pNewWord = reinterpret_cast<Word*>(malloc(sizeof(Word)));

但是,如果您坚持使用 C++ 并使用 new 运算符,则该行将缩减为:

Word *pNewWord = new Word;

这不仅会分配所需的内存,还会调用 class 构造函数,这在您的情况下是默认的。

我的出发点是构建目前只是 std::string 的代理 class:

class Word
{
public:
    std::size_t getSize() const; // in place of your 'len' member.

private:
    std::string m_data;
};

然后我将构建我的程序的其余部分,并且 只有在 如果我的 Word class cf 出现性能问题时才构建。该程序的其他区域我会尝试重构它,用其他东西替换 m_data

我认为这是不必要的,而您对 C++ 标准库对象没有足够性能的怀疑是没有根据的。

指针不是您的问题,这是因为将一个对象分配到此处而将另一个对象分配到堆上的其他位置以用于实际字符串,从而导致缓存命中率不足。如果您可以连续分配它,您将处于最佳缓存命中区域。

我知道你说没有树,但如果你写了一个自定义分配器并将其插入 std::map 或 std::set 你可以实现这个功能。

你应该查看关于竞技场分配器的 cppcon 演讲。

“CppCon 2017:John Lakos“本地 ('Arena') 内存分配器”

实现此目的的一种"simpler"方法是维护两个向量,分别用于 Word 和 "insert chunk aligned vector for allocating range for LetterData"。 Word 将获得一个更多的字段,该字段将偏移到 std::vector(或您选择的任何段大小)。您只需将 new 放入该范围内,也可以忽略析构函数,这会加快速度。

我敢打赌,一旦您增加了上下文对象,它所需要的 space 将与 map/set 节点一样多。

由于您将向其中塞入大量数据,因此预先预分配内存是有意义的。也许您可以通过在 slab 中分配一组连续的范围来实现高性能,而不是最终需要 "resized" 的一个大的连续范围,这需要创建一个新范围并将所有内容复制到它。

在开始编写自定义堆栈之前,对所有事物进行测量、测量和测量。生成一系列随机字长、搜索子集、计时等

使用开放大小的数组作为结构的最后一个成员在 C 中是很常见的做法。它在 C99 中被标准化(2.6.7,“灵活数组成员”)。

C++ 标准对“灵活的数组成员”只字不提。所以不能保证它在 C++ 中工作。但即使是 C++,大多数流行的编译器也支持此功能:

  • gcc
  • msvc
  • clang“请注意,clang 确实支持灵活的数组成员(在结构末尾具有零或未指定大小的数组)”

所以实际上可以使用它,但这是非常危险的做法。你应该尽量避免它,如果你决定使用它,你需要非常小心。

如果您使用此技术,有几个潜在问题:

  • 内存缓冲区的任何重新分配或删除都会使您的对象无效。
  • 您的 class 必须是不可复制的。任何副本(当您将对象作为参数传递时通常是隐式副本)都会使对象无效。您必须禁止复制构造函数和赋值运算符。
  • 继承是不可能的,你必须禁止它。
  • 您应该正确初始化所有数据以避免垃圾(也许用零填充缓冲区而不是使用放置新运算符)
  • 该方法常用于对象的序列化、保存和恢复,但不可移植。您需要考虑成员的大小和 big/little 字节序。

因此,如果可能的话,最好使用其他东西。例如,LetterData 的向量作为 class.

的成员的解决方案