Question

Au lieu de penser à initialiser une structure simple en "C", je pourrais en dériver et la mettre à zéro dans le constructeur comme suit:

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

Cette astuce est souvent utilisée pour initialiser des structures Win32 et peut parfois définir le membre cbSize omniprésent. .

Maintenant, tant qu’il n’ya pas de table de fonction virtuelle pour l’appel à détruire memset, est-ce une pratique sûre?

Était-ce utile?

La solution

PRÉAMBULE:

Bien que ma réponse soit toujours correcte, je trouve la réponse de litb tout à fait supérieur au mien parce que:

  1. Cela m'apprend un truc que je ne connaissais pas (les réponses de litb ont généralement cet effet, mais c'est la première fois que je l'écris)
  2. Cela répond exactement à la question (c'est-à-dire, initialiser à zéro la partie de la structure d'origine)

Alors s'il vous plaît, considérez la réponse de litb avant la mienne. En fait, je suggère à l'auteur de la question de considérer la réponse de litb comme étant la bonne.

Réponse originale

Mettre un objet true (c'est-à-dire std :: string) etc. à l'intérieur se brisera, car l'objet true sera initialisé avant le memset, puis écrasé par des zéros.

L'utilisation de la liste d'initialisation ne fonctionne pas pour g ++ (je suis surpris ...). Initialisez-le à la place dans le corps du constructeur CMyStruct. Ce sera convivial C ++:

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

P.S .: J'ai supposé que vous aviez le non contrôle sur MY_STRUCT, bien sûr. Avec le contrôle, vous auriez ajouté le constructeur directement dans MY_STRUCT et oublié l’héritage. Notez que vous pouvez ajouter des méthodes non virtuelles à une structure de type C, tout en conservant son comportement.

EDIT: Ajout de parenthèses manquantes après le commentaire de Lou Franco. Merci!

EDIT 2: J'ai essayé le code sur g ++, et pour une raison quelconque, l'utilisation de la liste d'initialisation ne fonctionne pas. J'ai corrigé le code en utilisant le constructeur de corps. La solution est toujours valable, cependant.

Veuillez réévaluer mon message car le code d'origine a été modifié (voir le journal des modifications pour plus d'informations).

EDIT 3: Après avoir lu le commentaire de Rob, je suppose qu’il a un point qui mérite d’être discuté: "Je suis d’accord, mais cela pourrait être une énorme structure Win32 qui pourrait changer avec un nouveau SDK, ainsi un memset est une preuve pour le futur."

Je ne suis pas d'accord: connaissant Microsoft, cela ne changera pas car ils ont besoin d'une compatibilité ascendante parfaite. Ils créeront à la place une structure étendue MY_STRUCT Ex avec la même présentation initiale que MY_STRUCT, avec des membres supplémentaires à la fin et reconnaissable par le biais de la "taille". variable membre comme la structure utilisée pour RegisterWindow, IIRC.

Donc, le seul point valable qui reste du commentaire de Rob est le "énorme" struct. Dans ce cas, un memset est peut-être plus pratique, mais vous devrez faire de MY_STRUCT un membre variable de CMyStruct au lieu d'en hériter.

Je vois un autre hack, mais je suppose que cela casserait à cause d'un problème possible d'alignement de la structure.

EDIT 4: Regardez la solution de Frank Krueger. Je ne peux pas vous promettre que c'est portable (je suppose que c'est le cas), mais cela reste intéressant d'un point de vue technique car il montre un cas où, en C ++, le "this" pointeur & adresse; adresse " passe de sa classe de base à sa classe héritée.

Autres conseils

Vous pouvez simplement initialiser la base par valeur, et tous ses membres seront mis à zéro. Ceci est garanti

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

Pour que cela fonctionne, il ne devrait y avoir aucun constructeur déclaré par l'utilisateur dans la classe de base, comme dans votre exemple.

Pas de memset méchant pour cela. Il n'est pas garanti que memset fonctionne dans votre code, même si cela devrait fonctionner dans la pratique.

Bien mieux qu’un memset, vous pouvez utiliser ce petit truc à la place:

MY_STRUCT foo = { 0 };

Ceci initialisera tous les membres sur 0 (ou leur valeur par défaut iirc), inutile de spécifier une valeur pour chacun.

Cela me rassurerait beaucoup car cela devrait fonctionner même s'il y a une vtable (sinon le compilateur va crier).

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

Je suis sûr que votre solution fonctionnera, mais je doute qu'il soit possible de garantir le mélange de memset et de classes.

C’est un exemple parfait de portage d’un idiome C en C ++ (et pourquoi cela ne fonctionne pas toujours ...)

Le problème que vous aurez avec l'utilisation de memset est qu'en C ++, une structure et une classe sont exactement la même chose, sauf que par défaut, une structure a une visibilité publique et une classe a une visibilité privée.

Ainsi, si par la suite un programmeur bien intentionné modifie MY_STRUCT de la manière suivante:


struct MY_STRUCT
{
    int n1;
    int n2;

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

En ajoutant cette fonction unique, votre memset pourrait maintenant causer des dégâts. Une discussion détaillée se trouve dans comp.lang.c +

Les exemples ont un "comportement non spécifié".

Pour un non-POD, l'ordre dans lequel le compilateur présente un objet (toutes les classes de base et tous les membres) n'est pas spécifié (ISO C ++ 10/3). Considérez ce qui suit:

struct A {
  int i;
};

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

private:
  int j;
};

Ceci peut être présenté comme suit:

[ int i ][ int j ]

Ou en tant que:

[ int j ][ int i ]

Par conséquent, l’utilisation de memset directement sur l’adresse de 'this' constitue un comportement extrêmement non spécifié. L'une des réponses ci-dessus, à première vue, semble être plus sûre:

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

Cependant, j’estime que, à proprement parler, cela entraîne également un comportement indéterminé. Je ne trouve pas le texte normatif, mais la note 10/5 indique: "Un sous-objet de classe de base peut avoir une présentation (3.7) différente de celle d'un objet le plus dérivé du même type".

En conséquence, le compilateur peut effectuer des optimisations d'espace avec les différents membres:

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

Peut être présenté comme suit:

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

Sur un système 32 bits, en raison d'alignements, etc. pour "B", la taille de (B) sera probablement de 8 octets. Cependant, sizeof (C) peut également être égal à 8 octets si le compilateur compresse les données membres. Par conséquent, l'appel à memset pourrait remplacer la valeur donnée à "c1".

La disposition précise d'une classe ou d'une structure n'est pas garantie en C ++, raison pour laquelle vous ne devez pas présumer de sa taille de l'extérieur (cela signifie que si vous n'êtes pas un compilateur).

Cela fonctionne probablement jusqu'à ce que vous trouviez un compilateur sur lequel il ne fonctionne pas, ou que vous jetiez une vtable dans le mélange.

Si vous avez déjà un constructeur, pourquoi ne pas l'initialiser ici avec n1 = 0; n2 = 0; - c’est certainement le moyen le plus normal .

Edit: En fait, comme l’a montré paercebal, l’initialisation de ctor est encore meilleure.

Mon avis est non. Je ne suis pas sûr de ce qu'il gagne non plus.

À mesure que votre définition de CMyStruct change et que vous ajoutez / supprimez des membres, cela peut entraîner des bogues. Facilement.

Créez un constructeur pour CMyStruct qui prend un MyStruct avec un paramètre.

CMyStruct::CMyStruct(MyStruct &)

Ou quelque chose de cela recherché. Vous pouvez ensuite initialiser un membre 'MyStruct' public ou privé.

D'un point de vue ISO C ++, deux problèmes se posent:

(1) L'objet est-il un POD? L'acronyme signifie Plain Old Data, et la norme énumère ce que vous ne pouvez pas avoir dans un POD (Wikipedia a un bon résumé). Si ce n'est pas un POD, vous ne pouvez pas le memset.

(2) Y a-t-il des membres pour lesquels tout-bits-zéro est invalide? Sous Windows et Unix, le pointeur NULL est composé de zéro. ce n'est pas nécessaire. Le point flottant 0 contient tous les bits nuls dans IEEE754, ce qui est assez courant, et sur x86.

Le conseil de Frank Kruegers répond à vos préoccupations en limitant le memset à la base POD de la classe non-POD.

Essayez ceci - surcharge nouvelle.

EDIT: Je devrais ajouter - C’est sûr, car la mémoire est mise à zéro avant que tous les constructeurs soient appelés. Gros défaut - ne fonctionne que si l'objet est alloué dynamiquement.

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 est votre code et que vous utilisez un compilateur C ++, vous pouvez y placer le constructeur sans insérer de classe:

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

Je ne suis pas sûr de l'efficacité, mais je déteste faire des trucs quand vous n'avez pas encore prouvé que l'efficacité est nécessaire.

Commentez la réponse de litb (semble-t-il ne suis pas encore autorisé à commenter directement):

Même avec cette belle solution de style C ++, vous devez faire très attention à ne pas l'appliquer naïvement à une structure contenant un membre non-POD.

Certains compilateurs ne s’initialisent donc plus correctement.

Voir cette réponse à une question similaire . J'ai personnellement eu la mauvaise expérience sur VC2008 avec un std :: string supplémentaire.

Ce que je fais est d'utiliser l'initialisation d'agrégat, mais en spécifiant uniquement les initialiseurs pour les membres qui comptent, par exemple:

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

Les membres restants seront initialisés à des zéros du type approprié (comme dans le message de Drealmer). Ici, vous faites confiance à Microsoft pour ne pas rompre gratuitement la compatibilité en ajoutant de nouveaux membres de structure au milieu (hypothèse raisonnable). Cette solution me semble optimale: une instruction, aucune classe, aucun memset, aucune hypothèse concernant la représentation interne des pointeurs à virgule flottante nuls ou nuls.

Je pense que les hacks impliquant un héritage sont un style horrible. Héritage public signifie IS-A pour la plupart des lecteurs. Notez également que vous héritez d'une classe qui n'est pas conçue pour être une base. En l'absence de destructeur virtuel, les clients qui suppriment une instance de classe dérivée via un pointeur vers la base invoqueront un comportement indéfini.

Je suppose que la structure vous a été fournie et ne peut pas être modifiée. Si vous pouvez modifier la structure, la solution évidente consiste à ajouter un constructeur.

Ne modifiez pas votre code avec des wrappers C ++ lorsque tout ce que vous voulez est une simple macro pour initialiser votre structure.

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

C'est un peu de code, mais il est réutilisable; incluez-le une fois et cela devrait fonctionner pour n'importe quel POD. Vous pouvez passer une instance de cette classe à toute fonction qui attend un MY_STRUCT ou utiliser la fonction GetPointer pour la transmettre à une fonction qui modifiera la structure.

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 cette façon, vous n'avez pas à vous demander si les valeurs NULL sont toutes égales à 0 ou sous la forme de représentations à virgule flottante. Tant que STR est un type d'agrégat simple, les choses vont fonctionner. Lorsque STR n’est pas un simple type d’agrégat, vous obtiendrez une erreur lors de la compilation, de sorte que vous n’aurez plus à vous soucier de l’utiliser abusivement. De plus, si le type contient quelque chose de plus complexe, tant qu'il a un constructeur par défaut, vous êtes ok:

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

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

En revanche, il est plus lent car vous effectuez une copie temporaire supplémentaire, et le compilateur affectera chaque membre à 0 individuellement, au lieu d'un memset.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top