Pregunta

En lugar de tener que recordar inicializar una estructura 'C' simple, podría derivarla y ponerla a cero en el constructor de esta manera:

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        memset(this, 0, sizeof(MY_STRUCT));
    }
};

Este truco se utiliza a menudo para inicializar estructuras Win32 y, a veces, puede configurar el omnipresente cbTamaño miembro.

Ahora bien, siempre que no haya una tabla de funciones virtuales que la llamada memset pueda destruir, ¿es esta una práctica segura?

¿Fue útil?

Solución

PREÁMBULO:

Si bien mi respuesta sigue siendo Ok, encuentro la respuesta de litb bastante superior al mío porque:

  1. Me enseña un truco que no sabía (las respuestas de litb suelen tener este efecto, pero esta es la primera vez que lo escribo)
  2. Responde exactamente la pregunta (es decir, inicializando la parte de la estructura original a cero)

Entonces, por favor, considere la respuesta de litb antes que la mía.De hecho, sugiero al autor de la pregunta que considere la respuesta de litb como la correcta.

Respuesta original

Poner un objeto verdadero (es decirstd::cadena), etc.El interior se romperá, porque el objeto verdadero se inicializará antes del memset y luego se sobrescribirá con ceros.

Usar la lista de inicialización no funciona para g++ (me sorprende...).En su lugar, inicialícelo en el cuerpo del constructor CMyStruct.Será compatible con C++:

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct() { n1 = 0 ; n2 = 0 ; }
};

PD.:Supuse que sí tenías No control sobre MY_STRUCT, por supuesto.Con control, habría agregado el constructor directamente dentro de MY_STRUCT y se habría olvidado de la herencia.Tenga en cuenta que puede agregar métodos no virtuales a una estructura tipo C y aún así hacer que se comporte como una estructura.

EDITAR:Se agregó el paréntesis faltante, después del comentario de Lou Franco.¡Gracias!

EDITAR 2:Probé el código en g++ y, por alguna razón, usar la lista de inicialización no funciona.Corregí el código usando el constructor del cuerpo.Sin embargo, la solución sigue siendo válida.

Vuelva a evaluar mi publicación, ya que se cambió el código original (consulte el registro de cambios para obtener más información).

EDITAR 3:Después de leer el comentario de Rob, supongo que tiene un punto digno de discusión:"De acuerdo, pero esta podría ser una estructura Win32 enorme que puede cambiar con un nuevo SDK, por lo que un memset es una prueba de futuro".

No estoy de acuerdo:Conociendo a Microsoft, no cambiará debido a su necesidad de una perfecta compatibilidad con versiones anteriores.En su lugar, crearán un MY_STRUCT extendidoEx Estructura con el mismo diseño inicial que MY_STRUCT, con miembros adicionales al final y reconocible a través de una variable miembro de "tamaño" como la estructura utilizada para RegisterWindow, IIRC.

Entonces, el único punto válido que queda del comentario de Rob es la estructura "enorme".En este caso, quizás un memset sea más conveniente, pero tendrá que hacer de MY_STRUCT un miembro variable de CMyStruct en lugar de heredar de él.

Veo otro truco, pero supongo que se rompería debido a un posible problema de alineación de la estructura.

EDITAR 4:Eche un vistazo a la solución de Frank Krueger.No puedo prometer que sea portátil (supongo que lo es), pero sigue siendo interesante desde un punto de vista técnico porque muestra un caso en el que, en C++, la "dirección" del puntero "esta" se mueve de su clase base a su clase heredada. .

Otros consejos

Simplemente puede inicializar el valor de la base y todos sus miembros se pondrán a cero.esto esta garantizado

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct():MY_STRUCT() { }
};

Para que esto funcione, no debe haber ningún constructor declarado por el usuario en la clase base, como en su ejemplo.

nada desagradable memset para eso.No está garantizado que memset funciona en su código, aunque debería funcionar en la práctica.

Mucho mejor que un memset, puedes usar este pequeño truco en su lugar:

MY_STRUCT foo = { 0 };

Esto inicializará todos los miembros a 0 (o su valor predeterminado iirc), no es necesario especificar un valor para cada uno.

Esto me haría sentir mucho más seguro ya que debería funcionar incluso si hay un vtable (o el compilador gritará).

memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Estoy seguro de que su solución funcionará, pero dudo que haya garantías al mezclar memset y clases.

Este es un ejemplo perfecto de cómo trasladar un modismo de C a C++ (y por qué es posible que no siempre funcione...)

El problema que tendrá al usar memset es que en C++, una estructura y una clase son exactamente lo mismo excepto que, de forma predeterminada, una estructura tiene visibilidad pública y una clase tiene visibilidad privada.

Por lo tanto, ¿qué pasa si más adelante algún programador bien intencionado cambia MY_STRUCT de esta manera?


struct MY_STRUCT
{
    int n1;
    int n2;

   // Provide a default implementation...
   virtual int add() {return n1 + n2;}  
};

Al agregar esa única función, su memset ahora podría causar estragos.Hay una discusión detallada en comp.lang.c+

Los ejemplos tienen "comportamiento no especificado".

Para un no POD, el orden en el que el compilador presenta un objeto (todas las clases y miembros básicos) no está especificado (ISO C++ 10/3).Considera lo siguiente:

struct A {
  int i;
};

class B : public A {       // 'B' is not a POD
public:
  B ();

private:
  int j;
};

Esto se puede presentar como:

[ int i ][ int j ]

O como:

[ int j ][ int i ]

Por lo tanto, usar memset directamente en la dirección de 'esto' es un comportamiento en gran medida no especificado.Una de las respuestas anteriores, a primera vista, parece más segura:

 memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Sin embargo, creo que, estrictamente hablando, esto también da lugar a un comportamiento no especificado.No encuentro el texto normativo, sin embargo la nota del 5/10 dice:"Un subobjeto de clase base puede tener un diseño (3.7) diferente al diseño de un objeto más derivado del mismo tipo".

Como resultado, el compilador pudo realizar optimizaciones de espacio con los diferentes miembros:

struct A {
  char c1;
};

struct B {
  char c2;
  char c3;
  char c4;
  int i;
};

class C : public A, public B
{
public:
  C () 
  :  c1 (10);
  {
    memset(static_cast<B*>(this), 0, sizeof(B));      
  }
};

Se puede presentar como:

[ char c1 ] [ char c2, char c3, char c4, int i ]

En un sistema de 32 bits, debido a alineaciones, etc.para 'B', el tamaño de (B) probablemente será de 8 bytes.Sin embargo, sizeof(C) también puede ser '8' bytes si el compilador empaqueta los miembros de datos.Por lo tanto, la llamada a memset podría sobrescribir el valor dado a 'c1'.

El diseño preciso de una clase o estructura no está garantizado en C++, por lo que no debes hacer suposiciones sobre su tamaño desde fuera (es decir, si no eres un compilador).

Probablemente funcione, hasta que encuentre un compilador en el que no funcione, o agregue algo de vtable a la mezcla.

Si ya tiene un constructor, ¿por qué no inicializarlo allí con n1=0;n2=0;-- eso es ciertamente lo más normal forma.

Editar:En realidad, como ha demostrado paercebal, la inicialización de ctor es aún mejor.

Mi opinión es no.Tampoco estoy seguro de qué gana.

A medida que cambia su definición de CMyStruct y agrega o elimina miembros, esto puede provocar errores.Fácilmente.

Cree un constructor para CMyStruct que tome un MyStruct que tenga un parámetro.

CMyStruct::CMyStruct(MyStruct &)

O algo de eso buscado.Luego puede inicializar un miembro 'MyStruct' público o privado.

Desde el punto de vista de ISO C++, hay dos problemas:

(1) ¿Es el objeto un POD?El acrónimo significa Plain Old Data y el estándar enumera lo que no se puede tener en un POD (Wikipedia tiene un buen resumen).Si no es un POD, no puedes memorizarlo.

(2) ¿Hay miembros para los cuales todos los bits cero no son válidos?En Windows y Unix, el puntero NULL tiene todos los bits cero;no tiene por qué ser así.El punto flotante 0 tiene todos los bits cero en IEEE754, que es bastante común, y en x86.

El consejo de Frank Kruegers aborda sus inquietudes restringiendo el conjunto de memorias a la base POD de la clase que no es POD.

Pruebe esto: sobrecargue lo nuevo.

EDITAR:Debo agregar: esto es seguro porque la memoria se pone a cero antes cualquier Se llaman constructores.Gran defecto: sólo funciona si el objeto se asigna dinámicamente.

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        // whatever
    }
    void* new(size_t size)
    {
        // dangerous
        return memset(malloc(size),0,size);
        // better
        if (void *p = malloc(size))
        {
            return (memset(p, 0, size));
        }
        else
        {
            throw bad_alloc();
        }
    }
    void delete(void *p, size_t size)
    {
        free(p);
    }

};

Si MY_STRUCT es su código y está satisfecho con el uso de un compilador de C++, puede colocar el constructor allí sin incluirlo en una clase:

struct MY_STRUCT
{
  int n1;
  int n2;
  MY_STRUCT(): n1(0), n2(0) {}
};

No estoy seguro de la eficiencia, pero odio hacer trucos cuando no has demostrado que la eficiencia es necesaria.

Comentar en la respuesta de litb (parece que todavía no puedo comentar directamente):

Incluso con esta agradable solución estilo C++, debes tener mucho cuidado de no aplicar esto ingenuamente a una estructura que contenga un miembro que no sea POD.

Algunos compiladores ya no se inicializan correctamente.

Ver esta respuesta a una pregunta similar.Personalmente tuve una mala experiencia en VC2008 con un std::string adicional.

Lo que hago es utilizar la inicialización agregada, pero solo especificando inicializadores para los miembros que me interesan, por ejemplo:

STARTUPINFO si = {
    sizeof si,      /*cb*/
    0,              /*lpReserved*/
    0,              /*lpDesktop*/
    "my window"     /*lpTitle*/
};

Los miembros restantes se inicializarán con ceros del tipo apropiado (como en la publicación de Drealmer).En este caso, usted confía en que Microsoft no romperá la compatibilidad de forma gratuita agregando nuevos miembros de estructura en el medio (una suposición razonable).Esta solución me parece óptima: una declaración, sin clases, sin memset, sin suposiciones sobre la representación interna de punteros cero o nulos de punto flotante.

Creo que los trucos relacionados con la herencia tienen un estilo horrible.La herencia pública significa IS-A para la mayoría de los lectores.Tenga en cuenta también que está heredando de una clase que no está diseñada para ser una base.Como no hay un destructor virtual, los clientes que eliminan una instancia de clase derivada a través de un puntero a la base invocarán un comportamiento indefinido.

Supongo que se le proporciona la estructura y no se puede modificar.Si puedes cambiar la estructura, entonces la solución obvia es agregar un constructor.

No diseñe demasiado su código con contenedores de C++ cuando todo lo que desea es una macro simple para inicializar su estructura.

#include <stdio.h>

#define MY_STRUCT(x) MY_STRUCT x = {0}

struct MY_STRUCT
{
    int n1;
    int n2;
};

int main(int argc, char *argv[])
{
    MY_STRUCT(s);

    printf("n1(%d),n2(%d)\n", s.n1, s.n2);

    return 0;
}

Es un poco de código, pero es reutilizable;inclúyalo una vez y debería funcionar para cualquier POD.Puede pasar una instancia de esta clase a cualquier función que espere MY_STRUCT, o usar la función GetPointer para pasarla a una función que modificará la estructura.

template <typename STR>
class CStructWrapper
{
private:
    STR MyStruct;

public:
    CStructWrapper() { STR temp = {}; MyStruct = temp;}
    CStructWrapper(const STR &myStruct) : MyStruct(myStruct) {}

    operator STR &() { return MyStruct; }
    operator const STR &() const { return MyStruct; }

    STR *GetPointer() { return &MyStruct; }
};

CStructWrapper<MY_STRUCT> myStruct;
CStructWrapper<ANOTHER_STRUCT> anotherStruct;

De esta manera, no tiene que preocuparse por si los NULL son todos 0 o representaciones de punto flotante.Mientras STR sea un tipo agregado simple, todo funcionará.Cuando STR no es un tipo agregado simple, obtendrá un error en tiempo de compilación, por lo que no tendrá que preocuparse por hacer un mal uso accidental de esto.Además, si el tipo contiene algo más complejo, siempre y cuando tenga un constructor predeterminado, está bien:

struct MY_STRUCT2
{
    int n1;
    std::string s1;
};

CStructWrapper<MY_STRUCT2> myStruct2; // n1 is set to 0, s1 is set to "";

La desventaja es que es más lento ya que estás haciendo una copia temporal adicional y el compilador asignará a cada miembro 0 individualmente, en lugar de un memset.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top