Domanda

Invece di dover ricordare di inizializzare una semplice struttura a "C", potrei derivarne da essa e azzerarla nel costruttore in questo modo:

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

Questo trucco viene spesso utilizzato per inizializzare strutture Win32 e talvolta può impostare l'onnipresente membro cbSize .

Ora, fintanto che non esiste una tabella delle funzioni virtuali da distruggere per la chiamata memset, è una pratica sicura?

È stato utile?

Soluzione

PREAMBOLO:

Mentre la mia risposta è ancora ok, trovo la risposta di litb abbastanza superiore al mio perché:

  1. Mi insegna un trucco che non conoscevo (le risposte di litb di solito hanno questo effetto, ma questa è la prima volta che lo scrivo)
  2. Risponde esattamente alla domanda (ovvero inizializzando la parte della struttura originale a zero)

Quindi, per favore, considera la risposta di litb prima della mia. In effetti, suggerisco all'autore della domanda di considerare la risposta di litb come quella giusta.

Risposta originale

L'inserimento di un oggetto vero (cioè std :: string) ecc. all'interno si interromperà, poiché l'oggetto vero verrà inizializzato prima del memset e quindi sovrascritto da zero.

L'uso dell'elenco di inizializzazione non funziona per g ++ (sono sorpreso ...). Inizializzalo invece nel corpo del costruttore CMyStruct. Sarà compatibile con C ++:

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

P.S .: Suppongo che tu abbia nessun controllo su MY_STRUCT, ovviamente. Con il controllo, avresti aggiunto il costruttore direttamente all'interno di MY_STRUCT e ti saresti dimenticato dell'eredità. Nota che puoi aggiungere metodi non virtuali a una struttura simile a C e farla comunque comportare come una struttura.

EDIT: aggiunta parentesi mancante, dopo il commento di Lou Franco. Grazie!

MODIFICA 2: ho provato il codice su g ++ e, per qualche motivo, l'utilizzo dell'elenco di inizializzazione non funziona. Ho corretto il codice usando il costruttore del corpo. La soluzione è comunque valida.

Rivaluta il mio post, poiché il codice originale è stato modificato (vedi il registro delle modifiche per ulteriori informazioni).

MODIFICA 3: Dopo aver letto il commento di Rob, immagino che abbia un punto degno di discussione: "D'accordo, ma questa potrebbe essere un'enorme struttura Win32 che può cambiare con un nuovo SDK, quindi un memset è una prova futura."

Non sono d'accordo: conoscendo Microsoft, non cambierà a causa della loro necessità di una perfetta compatibilità con le versioni precedenti. Creeranno invece una struttura MY_STRUCT Ex estesa con lo stesso layout iniziale di MY_STRUCT, con membri aggiuntivi alla fine e riconoscibile attraverso una "dimensione". variabile membro come la struttura utilizzata per RegisterWindow, IIRC.

Quindi l'unico punto valido rimasto dal commento di Rob è il "enorme". struct. In questo caso, forse un memset è più conveniente, ma dovrai rendere MY_STRUCT un membro variabile di CMyStruct invece di ereditare da esso.

Vedo un altro hack, ma immagino che questo si spezzerebbe a causa del possibile problema di allineamento della struttura.

MODIFICA 4: dai un'occhiata alla soluzione di Frank Krueger. Non posso promettere che sia portatile (immagino che lo sia), ma è comunque interessante da un punto di vista tecnico perché mostra un caso in cui, in C ++, il "questo" puntatore " indirizzo " passa dalla sua classe base alla sua classe ereditata.

Altri suggerimenti

Puoi semplicemente inizializzare il valore della base e tutti i suoi membri verranno azzerati. Questo è garantito

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

Perché questo funzioni, non ci dovrebbe essere nessun costruttore dichiarato dalla classe base, come nel tuo esempio.

Nessun cattivo memset per questo. Non è garantito che memset funzioni nel tuo codice, anche se dovrebbe funzionare nella pratica.

Molto meglio di un memset, puoi usare questo piccolo trucco:

MY_STRUCT foo = { 0 };

Questo inizializzerà tutti i membri su 0 (o il loro valore predefinito iirc), non è necessario specificare un valore per ciascuno.

Questo mi farebbe sentire molto più sicuro perché dovrebbe funzionare anche se c'è un vtable (o il compilatore urlerà).

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

Sono sicuro che la tua soluzione funzionerà, ma dubito che ci siano garanzie da fare quando si mescolano memset e classi.

Questo è un esempio perfetto di porting di un linguaggio C in C ++ (e perché potrebbe non funzionare sempre ...)

Il problema che avrai con l'utilizzo di memset è che in C ++, una struttura e una classe sono esattamente la stessa cosa, tranne che per impostazione predefinita, una struttura ha visibilità pubblica e una classe ha visibilità privata.

Quindi, se poi, un programmatore ben intenzionato cambia MY_STRUCT in questo modo:


struct MY_STRUCT
{
    int n1;
    int n2;

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

Aggiungendo quella singola funzione, il tuo memset potrebbe ora causare il caos. C'è una discussione dettagliata in comp.lang.c +

Gli esempi hanno " comportamento non specificato " ;.

Per un non-POD, l'ordine con cui il compilatore presenta un oggetto (tutte le classi e i membri di base) non è specificato (ISO C ++ 10/3). Considera quanto segue:

struct A {
  int i;
};

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

private:
  int j;
};

Questo può essere strutturato come:

[ int i ][ int j ]

O come:

[ int j ][ int i ]

Pertanto, l'utilizzo di memset direttamente sull'indirizzo di 'this' è un comportamento molto non specificato. Una delle risposte sopra, a prima vista sembra essere più sicura:

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

Credo, tuttavia, che anche a rigor di termini ciò comporti un comportamento non specificato. Non riesco a trovare il testo normativo, tuttavia la nota in 10/5 dice: "Un oggetto secondario di classe base può avere un layout (3.7) diverso dal layout di un oggetto più derivato dello stesso tipo".

Di conseguenza, il compilatore potrebbe eseguire ottimizzazioni dello spazio con i diversi membri:

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

Può essere strutturato come:

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

Su un sistema a 32 bit, a causa di accensioni ecc. per "B", la dimensione di (B) sarà molto probabilmente di 8 byte. Tuttavia, sizeof (C) può anche essere '8' byte se il compilatore comprime i membri dei dati. Pertanto la chiamata a memset potrebbe sovrascrivere il valore dato a 'c1'.

Il layout preciso di una classe o struttura non è garantito in C ++, motivo per cui non dovresti fare ipotesi sulla sua dimensione dall'esterno (ciò significa che se non sei un compilatore).

Probabilmente funziona, fino a quando non trovi un compilatore sul quale non lo fa, o butti qualche vtable nel mix.

Se hai già un costruttore, perché non inizializzarlo lì con n1 = 0; n2 = 0; - questo è certamente il modo più normale .

Modifica: in realtà, come ha dimostrato Paercebal, l'inizializzazione di ctor è ancora migliore.

La mia opinione è no. Non sono nemmeno sicuro di cosa guadagni.

Poiché la definizione di CMyStruct cambia e si aggiungono / eliminano membri, ciò può causare errori. Facilmente.

Crea un costruttore per CMyStruct che accetta un MyStruct ha un parametro.

CMyStruct::CMyStruct(MyStruct &)

O qualcosa del genere cercato. È quindi possibile inizializzare un membro "MyStruct" pubblico o privato.

Da un punto di vista ISO C ++, ci sono due problemi:

(1) L'oggetto è un POD? L'acronimo sta per Plain Old Data e lo standard elenca ciò che non si può avere in un POD (Wikipedia ha un buon riassunto). Se non è un POD, non puoi memorizzarlo.

(2) Ci sono membri per i quali all-bit-zero non è valido? Su Windows e Unix, il puntatore NULL è tutti i bit zero; non è necessario. Il virgola mobile 0 ha tutti i bit zero in IEEE754, che è abbastanza comune e su x86.

Il suggerimento di Frank Kruegers risolve i tuoi dubbi limitando il memset alla base POD della classe non POD.

Prova questo - sovraccarico nuovo.

EDIT: dovrei aggiungere - Questo è sicuro perché la memoria viene azzerata prima che venga chiamato qualsiasi costruttori. Grande difetto: funziona solo se l'oggetto è allocato dinamicamente.

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

};

Se MY_STRUCT è il tuo codice e sei soddisfatto dell'uso di un compilatore C ++, puoi inserire il costruttore senza includerlo in una classe:

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

Non sono sicuro dell'efficienza, ma odio fare i trucchi quando non hai dimostrato che l'efficienza è necessaria.

Commenta la risposta di litb (sembra I non posso ancora commentare direttamente):

Anche con questa bella soluzione in stile C ++ devi stare molto attento a non applicarlo ingenuamente a una struttura che contiene un membro non-POD.

Alcuni compilatori non si inizializzano più correttamente.

Vedi questa risposta a una domanda simile . Personalmente ho avuto la brutta esperienza su VC2008 con un ulteriore std :: string.

Quello che faccio è usare l'inizializzazione aggregata, ma specificando solo gli inizializzatori per i membri a cui tengo, ad esempio:

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

I membri rimanenti verranno inizializzati su zeri del tipo appropriato (come nel post di Drealmer). Qui, stai confidando che Microsoft non rompa gratuitamente la compatibilità aggiungendo nuovi membri della struttura nel mezzo (un presupposto ragionevole). Questa soluzione mi sembra ottimale: un'istruzione, nessuna classe, nessun memset, nessuna ipotesi sulla rappresentazione interna di virgola mobile zero o puntatori null.

Penso che gli hack che coinvolgono l'eredità siano uno stile orribile. Eredità pubblica significa IS-A per la maggior parte dei lettori. Nota anche che stai ereditando da una classe che non è progettata per essere una base. Poiché non esiste un distruttore virtuale, i client che eliminano un'istanza di classe derivata tramite un puntatore alla base invocheranno un comportamento indefinito.

Suppongo che la struttura ti sia fornita e non possa essere modificata. Se puoi cambiare la struttura, la soluzione ovvia è aggiungere un costruttore.

Non ingegnerizzare eccessivamente il tuo codice con wrapper C ++ quando tutto ciò che vuoi è una semplice macro per inizializzare la tua struttura.

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

È un po 'di codice, ma è riutilizzabile; includilo una volta e dovrebbe funzionare per qualsiasi POD. È possibile passare un'istanza di questa classe a qualsiasi funzione in attesa di MY_STRUCT o utilizzare la funzione GetPointer per passarla a una funzione che modificherà la struttura.

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;

In questo modo, non devi preoccuparti se i NULL sono tutti 0 o rappresentazioni in virgola mobile. Finché STR è un tipo aggregato semplice, le cose funzioneranno. Quando STR non è un tipo di aggregazione semplice, otterrai un errore in fase di compilazione, quindi non dovrai preoccuparti di utilizzarlo accidentalmente. Inoltre, se il tipo contiene qualcosa di più complesso, purché abbia un costruttore predefinito, stai bene:

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

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

Il rovescio della medaglia, è più lento poiché stai facendo una copia temporanea aggiuntiva e il compilatore assegnerà ogni membro a 0 individualmente, anziché a un memset.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top