2010-09-07 15 views
7

quitado la etiqueta de C, ya que estaba causando cierta confusión (que no debería haber estado allí, para empezar;. Historia de los foros no contestar C como sigue bienvenida aunque :)Creación de objetos de tamaño dinámicamente

En algunas cosas que he hecho, he encontrado la necesidad de crear objetos que tengan un tamaño dinámico y un tamaño estático, donde la parte estática son los miembros de tu objeto básico, la parte dinámica es, sin embargo, una matriz/búfer anexada directamente en la clase, manteniendo la memoria contigua, disminuyendo así la cantidad de asignaciones necesarias (estos son objetos no reubicables), y disminuyendo la fragmentación (aunque como un inconveniente, puede ser más difícil encontrar un bloque de un tamaño lo suficientemente grande, sin embargo eso es mucho más raro, si incluso debería Ccur en absoluto, que fragmentación de montón. Esto también es útil en dispositivos integrados donde la memoria es escasa (sin embargo, actualmente no hago nada para dispositivos integrados), y cosas como std :: string deben evitarse, o no pueden usarse como en el caso de uniones triviales.

Generalmente el camino me gustaría ir sobre esto sería (ab) uso de malloc (std :: string no se utiliza a propósito, y por diversas razones):

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)); 

Sin embargo, esto no lo hace siento muy bien conmigo, ya que en primer lugar me gustaría hacer esto usando el ambiente nuevo, y por lo tanto en un C++, y en segundo lugar, se ve horrible: P

así que el siguiente paso era una plantilla de C++ Varient:

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

Este h in embargo tiene el problema de que almacenar un puntero a un objeto de tamaño variable se hace 'imposible', por lo que entonces el siguiente trabajo en torno a:

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

Sin embargo, esto aún requiere de fundición, y tener punteros a DynamicObject por todo el lugar se convierte en ambiguo cuando se más de un objeto de tamaño dinámico se deriva de él (también se ve horrible y puede sufrir de un error que obliga a las clases vacías a seguir teniendo un tamaño, aunque es probable que sea un error arcaico, extinto ...).

Luego fue la siguiente:

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

struct TextCache : DynamicObject {...}; 

la que se ve mucho mejor, pero podría interferir con los objetos que ya tienen sobrecargas de nuevo (que incluso podría afectar a la colocación de nuevo ...).

Finalmente se me ocurrió con la colocación de nueva abusar:

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

Sin embargo, esto es probablemente la peor idea hasta el momento, para un buen número de razones.

¿Hay alguna forma mejor de hacerlo? o ¿alguna de las versiones anteriores sería mejor, o al menos mejorable? ¿Incluso se considera una práctica de programación segura y/o mala?


como he dicho anteriormente, yo estoy tratando de evitar dobles asignaciones, porque esto no debería necesitar 2 asignaciones, y hacer que este hace que escribir (serialización) estas cosas a los archivos mucho más fácil. La única excepción al requisito de asignación doble que tengo es cuando básicamente no hay gastos generales. La única causa en la que me he encontrado es que asigné memoria de forma secuencial desde un búfer fijo (using this system, que se me ocurrió), sin embargo, también es una excepción especial para evitar la copia superflua.

+1

¿Hay alguna razón por la que no desee utilizar STL? –

+1

@ Kornel: tal vez por la etiqueta 'C', pero los ejemplos sugieren que tal vez la etiqueta C sea superflua (aunque mi respuesta se concentre en C). –

+0

@Kornel: algunas de las cosas que tengo en mente no pueden usar STL, ya sea porque: a) usan asignadores que no son amigables con STL, ob) son asignadores ellos mismos, y usar STL derrotaría al todo el propósito de su existencia. – Necrolis

Respuesta

7

Me gustaría llegar a un compromiso con el concepto DynamicObject. Todo lo que no depende del tamaño va a la clase base.

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]; 
}; 

Esto debería reducir la cantidad de fundición necesaria.

3

C99 bendice el 'struct hack' - aka miembro de matriz flexible.

§6.7.2.1 Estructura y especificadores de unión

¶16 Como caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleta; esto se llama un miembro de matriz flexible. Con dos excepciones , se ignora el miembro de matriz flexible. Primero, el tamaño de la estructura debe ser igual al desplazamiento del último elemento de una estructura por lo demás idéntica que reemplaza al miembro de matriz flexible con una matriz de longitud no especificada. 106) Segundo, cuando a. (o ->) operador tiene un operando izquierdo que es (un puntero) una estructura con un miembro de matriz flexible y el operando derecho nombra ese miembro, se comporta como si ese miembro fuera reemplazado con la matriz más larga (con el mismo tipo de elemento) que no haría la estructura más grande que el objeto al que se accede; el desplazamiento de la matriz seguirá siendo el del miembro de matriz flexible , incluso si esto difiriera del de la matriz de reemplazo. Si esta matriz no tuviera elementos, se comporta como si tuviera un elemento pero el comportamiento es indefinido si se intenta acceder a ese elemento o generar un puntero pasado .

¶17 Ejemplo Suponiendo que todos los miembros de la matriz están alineados el mismo, después de las declaraciones:

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

las tres expresiones:

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

tienen el mismo valor. La estructura struct s tiene un miembro de matriz flexible d.

106) La longitud no se especifica para permitir el hecho de que las implementaciones pueden dar diferentes miembros de la matriz alineaciones de acuerdo con sus longitudes.

De lo contrario, utilice dos asignaciones separadas: una para los datos centrales en la estructura y la segunda para los datos adjuntos.

+0

Ve a buscar la cita estándar de C++. El OP claramente usa C++. – Puppy

+1

@DeadMG: Lo es, pero también lo etiquetó [c] así que c las respuestas son * en *; si al OP no le gusta, debería haber pensado mejor en primer lugar. No hay excusa para usar [c] [C++] a menos que lo diga en serio. – dmckee

+0

mis aplicaciones, la etiqueta C no debería haber estado allí, sin embargo, es bueno saber que el método que uso en los programas C es "claro" (aunque en C realmente no hay otra forma de hacerlo) – Necrolis

0

Creo que, en C++, técnicamente esto es Comportamiento no definido (debido a problemas de alineación), aunque sospecho que se puede hacer que funcione para todas las implementaciones existentes.

¿Pero por qué hacer eso de todos modos?

0

Usted podría utilizar placement new en C++:

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

La única salvedad es que es necesario eliminar buff en lugar de pCache y si pCache tiene un destructor que tendrá que llamar de forma manual.

Si tiene la intención de acceder a esta zona la capacidad utilizando pBuffer te recomiendo hacer esto:

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; 
0

No hay nada malo con la gestión de su propia memoria.

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. 
}; 

Debe tener en cuenta, sin embargo, que esta optimización es prematura y hay mucho mejores maneras de hacerlo, como tener una piscina al objeto o montón administrado. Además, no conté para ninguna alineación, sin embargo, entiendo que sizeof() devuelve el tamaño alineado. Además, esto será una perra para mantener para la construcción no trivial. Además, esto no está probado en absoluto. Un montón administrado es una mejor idea. Pero no debe tener miedo de administrar su propia memoria: si tiene requisitos de memoria personalizados, necesita administrar su propia memoria.

Me acabo de ocurrir que destruí pero no eliminé la memoria "extra".

+0

En realidad, sí tengo un montón administrado, sin embargo, cuanto menos llamadas mejor, ya que hay miles de allocs/libres por segundo solo de mi ejemplo anterior. Esto fue realmente implementado porque tuve una gran caída en fps (esto es parte de un motor de caché de texto para el motor de renderizado), con solo texto dibujado y una pequeña cantidad de texto para arrancar, sin embargo, después de hacer esta optimización, se triplicó en fps, que solucionó el cuello de botella que ralentizaba todo hasta detenerse casi – Necrolis

+0

El punto de tener un montón administrado es que los allocs son prácticamente gratuitos y la memoria asignada en él es contigua. Creo que si un montón administrado no funciona para ti (con esta muestra específica, no estoy diciendo que sean geniales para todos los propósitos), entonces debes volver a mirar tu montón administrado. – Puppy

+0

Los allocs secuenciales no están garantizados para ser comtiguos, al menos cuando uno usa algo así como un sistema de amigos binarios, que es lo que estoy usando, pero sí, los allocs están para ser (casi) libres. – Necrolis

2

Esto puede parecer herético con respecto a la mentalidad económica de los programadores C o C++, pero la última vez que tuve un problema similar para resolver lo seleccioné para poner un búfer estático de tamaño fijo en mi estructura y acceder a él mediante un puntero indirecto. Si la estructura proporcionada creció más que mi búfer estático, el puntero de indirección se asignó dinámicamente (y el búfer interno no se usó). Eso fue muy simple de implementar y resolvió el tipo de problemas que planteaste como la fragmentación, ya que el buffer estático se usaba en más del 95% del caso de uso real, y el 5% restante necesitaba grandes búferes, por lo que no me interesaba demasiado la pequeña pérdida de buffer interno.

+0

Este principio de pequeña asignación estática de memoria intermedia o de memoria intermedia cuando se requiere a menudo se denomina "optimización de cadena pequeña" para la clase de cadenas. Se usa (por ejemplo) en 'std :: string' de Visual Studio 2010. No sé si hay un nombre más genérico. –