创建动态大小的对象

时间:2010-09-07 15:13:35

标签: c++

删除了C标签,因为这引起了一些混淆(它不应该在那里开始;抱歉给那里带来任何不便。但C答案仍然很受欢迎:)

在我做过的一些事情中,我发现需要创建具有动态大小和静态大小的对象,其中静态部分是基本对象成员,但动态部分是数组/缓冲区直接附加到类上,保持内存连续,从而减少所需的分配量(这些是不可重新分配的对象),并减少碎片(虽然作为一个缺点,可能更难找到足够大的块但是,如果它甚至应该发生,那么这就比堆碎片更加罕见。这对于内存非常重要的嵌入式设备也很有用(但我目前对嵌入式设备没有任何作用),以及像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));

然而,这对我来说并不是很好,因为首先我想使用new,因此在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 {...};
然而,这仍然需要进行转换,并且当更多的动态大小的对象从它派生时,遍布整个地方的指向动态对象变得模棱两可(它看起来也很糟糕并且可能遭受强制空类仍然具有大小的错误,虽然这可能是一个古老的,灭绝的错误......)。

然后有这个:

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

struct TextCache : DynamicObject {...};

看起来好多了,但会干扰已经有新的重载的对象(甚至可能会影响新的放置......)。

最后我提出了新的滥用行为:

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

这可能是迄今为止最糟糕的想法,原因有很多。

那么还有更好的方法吗?或者上述版本之一会更好,还是至少可以改进?是否甚至考虑过安全和/或糟糕的编程习惯?


正如我上面所说,我试图避免双重分配,因为这不需要2次分配,因此这使得将这些东西写入(序列化)到文件更容易。 我所拥有的双重分配要求的唯一例外是其基本上为零开销。我遇到的唯一原因是我从一个固定的缓冲区(using this system顺序分配内存,我想出了它),但它也是一个特殊的例外,以防止超级复制。

6 个答案:

答案 0 :(得分:8)

我会与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]; 
}; 

这应该减少所需的铸件。

答案 1 :(得分:3)

C99祝福'struct hack' - 又名灵活的阵列成员

  

§6.7.2.1结构和联合说明符

     

¶16作为一种特殊情况,具有多个命名成员的结构的最后一个元素可以   有一个不完整的数组类型;这被称为灵活的阵列成员。有两个   异常,灵活的数组成员被忽略。首先,结构的大小应为   等于替换其他相同结构的最后一个元素的偏移量   具有未指定长度数组的灵活数组成员。 106)其次,当a。 (或 - &gt;)   operator有一个左操作数,它是一个带有灵活数组成员的结构(指向)   并且右边的操作数命名该成员,它的行为就像该成员被替换一样   最长的数组(具有相同的元素类型),不会构成结构   大于被访问的对象;数组的偏移量应保持为   灵活的阵列成员,即使这与替换阵列的成员不同。如果这   数组没有元素,它的行为好像它有一个元素,但行为是   如果尝试访问该元素或生成过去的指针,则为undefined   它

     

¶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具有灵活的数组成员d。

     

106)未指定长度以允许实现可能使数组成员不同   根据它们的长度进行对齐。

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

答案 2 :(得分:2)

关于C或C ++程序员的经济思维方式可能看起来很异议,但上次我有一个类似的问题要解决我选择在我的struct中放置一个固定大小的静态缓冲区并通过指针间接访问它。如果给定的结构比我的静态缓冲区大,则间接指针随后被动态分配(并且内部缓冲区未被使用)。这非常简单,并解决了你提出的问题,如碎片,因为静态缓冲区被用于超过95%的实际用例,剩下的5%需要非常大的缓冲区,因此我并不关心内部缓冲区的损失很小。

答案 3 :(得分:0)

我相信,在C ++中,技术上这是 Undefined Behavior (由于对齐问题),尽管我怀疑它可能适用于每个现有的实现。

但是为什么呢?

答案 4 :(得分:0)

您可以在C ++中使用placement new

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;

答案 5 :(得分:0)

管理自己的记忆没有错。

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()返回对齐的大小。而且,这将是维持非平凡建筑的婊子。此外,这是完全未经测试的。托管堆是一种更好的想法。但是你不应该害怕管理自己的内存 - 如果你有自定义内存要求,你需要管理自己的内存。

我突然意识到我已经破坏但没有删除“额外”记忆。