代码之家  ›  专栏  ›  技术社区  ›  Necrolis

创建动态大小的对象

c++
  •  10
  • Necrolis  · 技术社区  · 15 年前

    在我做的一些事情中,我发现需要创建具有动态大小和静态大小的对象,其中静态部分是基本对象成员,而动态部分是直接附加到类上的数组/缓冲区,保持内存连续,这样就减少了所需的分配量(这些是不可重分配的对象),并减少了碎片化(尽管作为一个缺点,可能很难找到足够大的块,但是这比堆碎片化要难得多——如果它真的应该发生的话)。这对于内存昂贵的嵌入式设备也很有帮助(但是我目前不为嵌入式设备做任何事情),像std::string这样的东西需要避免,或者不能像在普通联合的情况下那样使用。

    一般来说,我的方法是(ab)使用malloc(std::string不是故意使用的,原因有很多):

    struct TextCache
    {
        uint_32 fFlags;
        uint_16 nXpos;
        uint_16 nYpos;
         TextCache* pNext;
        char pBuffer[0];
    };
    
    TextCache* pCache = (TextCache*)malloc(sizeof(TextCache) + (sizeof(char) * nLength));
    

    然而,这并不是我的好主意,因为首先我想用新的方法来做这个,因此在C++环境下,第二,它看起来很可怕:P

    所以下一步是模板化的C++变量:

    template <const size_t nSize> struct TextCache
    {
        uint_32 fFlags;
        uint_16 nXpos;
        uint_16 nYpos;
         TextCache<nSize>* pNext;
        char pBuffer[nSize];
    };
    

    但是,这有一个问题,即存储指向可变大小对象的指针变得“不可能”,因此接下来的工作是:

    class DynamicObject {};
    template <const size_t nSize> struct TextCache : DynamicObject {...};
    

    然而,这仍然需要强制转换,当一个以上的动态大小的对象派生自DynamicObject时,到处都有指向DynamicObject的指针就变得模棱两可了(它看起来也很可怕,可能会遇到一个bug,迫使空类仍然有一个大小,尽管这可能是一个过时的、已灭绝的bug……)。

    然后是这样的:

    class DynamicObject
    {
        void* operator new(size_t nSize, size_t nLength)
        {
            return malloc(nSize + nLength);
        }
    };
    
    struct TextCache : DynamicObject {...};
    

    这看起来好多了,但是会干扰已经有new重载的对象(它甚至会影响new…)。

    最后我想到了新的虐待方式:

    inline TextCache* CreateTextCache(size_t nLength)
    {
        char* pNew = new char[sizeof(TextCache) + nLength];
        return new(pNew) TextCache;
    }
    

    有没有更好的方法?或者上面的一个版本会更好,或者至少可以改进?这样做是否被认为是安全和/或糟糕的编程实践?


    对于双重分配需求,我唯一的例外是它的开销基本为零。我遇到的唯一原因是我从固定缓冲区顺序分配内存( using this system

    6 回复  |  直到 15 年前
        1
  •  8
  •   Mark Ransom    15 年前

    我会和政府妥协的 DynamicObject 概念。所有不依赖于大小的东西都进入基类。

    struct TextBase 
    { 
        uint_32 fFlags; 
        uint_16 nXpos; 
        uint_16 nYpos; 
         TextBase* pNext; 
    }; 
    
    template <const size_t nSize> struct TextCache : public TextBase
    { 
        char pBuffer[nSize]; 
    }; 
    

        2
  •  3
  •   Jonathan Leffler    15 年前

    C99祝福“结构黑客”-又名 灵活数组成员 .

    §6.7.2.1结构和联合规范

    16作为特殊情况,具有一个以上命名构件的结构的最后一个构件可以 具有不完整的数组类型;这称为灵活数组成员。有两个 例外情况下,将忽略灵活数组成员。首先,结构的尺寸应 具有未指定长度数组的灵活数组成员。 106) 运算符的左操作数是(指向)具有灵活数组成员的结构 具有最长的数组(具有相同的元素类型),该数组不会构成结构 大于被访问的对象;数组的偏移量应保持被访问对象的偏移量 灵活的数组成员,即使这与替换数组的成员不同。如果这个 如果试图访问该元素或生成一个指针,则未定义

    17示例假设所有数组成员对齐方式相同,在声明之后:

    struct s { int n; double d[]; };
    struct ss { int n; double d[1]; };
    

     sizeof (struct s)
     offsetof(struct s, d)
     offsetof(struct ss, d)
    

    具有相同的值。结构struct s有一个灵活的数组成员d。

    106) 根据长度排列。

    否则,使用两个单独的分配—一个用于结构中的核心数据,另一个用于附加数据。

        3
  •  2
  •   kriss    15 年前

        4
  •  0
  •   sbi    15 年前

    我相信,在C++中,技术上就是这样。 未定义的行为 (由于对齐问题),尽管我怀疑它可能适用于所有现有的实现。

        5
  •  0
  •   Niki Yoshiuchi    15 年前

    你可以用 placement new 在C++中:

    char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)];
    TextCache *pCache = new (buff) TextCache;
    

    唯一的警告是你需要删除 buff pCache 如果 pCache公司

    如果您打算使用 pBuffer

    struct TextCache
    {
    ...
        char *pBuffer;
    };
    ...
    char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)];
    TextCache *pCache = new (buff) TextCache;
    pCache->pBuffer = new (buff + sizeof(TextCache)) char[nLength];
    ...
    delete [] buff;
    
        6
  •  0
  •   Puppy    15 年前

    管理你自己的记忆没有错。

    template<typename DerivedType, typename ElemType> struct appended_array {
        ElemType* buffer;
        int length;
        ~appended_array() {
            for(int i = 0; i < length; i++)
                buffer->~ElemType();
            char* ptr = (char*)this - sizeof(DerivedType);
            delete[] ptr;
        }
        static inline DerivedType* Create(int extra) {
            char* newbuf = new char[sizeof(DerivedType) + (extra * sizeof(ElemType))];
            DerivedType* ptr = new (newbuf) DerivedType();
            ElemType* extrabuf = (ElemType*)newbuf[sizeof(DerivedType)];          
            for(int i = 0; i < extra; i++)
                new (&extrabuf[i]) ElemType();
            ptr->lenghth = extra;
            ptr->buffer = extrabuf;
            return ptr;                    
        }
    };
    
    struct TextCache : appended_array<TextCache, char>
    {
        uint_32 fFlags;
        uint_16 nXpos;
        uint_16 nYpos;
        TextCache* pNext;
        // IT'S A MIRACLE! We have a buffer of size length and pointed to by buffer of type char that automagically appears for us in the Create function.
    };
    

    但是,您应该考虑到,这种优化是不成熟的,有更好的方法来实现它,比如拥有一个对象池或托管堆。另外,我没有计算任何对齐方式,但是我的理解是sizeof()返回对齐的大小。此外,这将是一个婊子维护非琐碎的建设。而且,这完全没有经过测试。托管堆是一种更好的方法。但是你不应该害怕管理你自己的内存——如果你有定制的内存需求,你需要管理你自己的内存。

    我突然想到,我破坏了,但没有删除“额外”的记忆。