Domanda

Spesso aggiungo un metodo Empty ai miei oggetti C ++ per cancellare lo stato interno usando un codice simile al seguente.

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo() : n_(1234), str_("Hello, world!")
    {
    }

    void Empty()
    {
        *this = Foo();
    }
};

Questo sembra essere meglio della duplicazione del codice nel costruttore, ma mi chiedevo se * this = Foo () è un approccio comune quando si desidera cancellare un oggetto? Ci sono problemi con questa attesa di mordermi sul retro? Esistono altri modi migliori per ottenere questo genere di cose?

È stato utile?

Soluzione

Vorrei invece che il costruttore chiamasse la mia funzione:

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo()
    {
        Reset();
    }

    void Reset()
    {
        n_ = 1234;
        str_ = "Hello, world!";
    }
};

Sì, stai inizializzando inutilmente la stringa come stringa vuota prima, quindi eseguendo un'assegnazione, ma questo è molto più chiaro.

Altri suggerimenti

Potenziali problemi? Come fai a sapere che * questo è davvero un Foo?

Quello che stai facendo con questo metodo Empty, è essenzialmente lo stesso dell'assegnazione manuale di un oggetto di nuova costruzione a una variabile (cosa che fa la funzione Empty).

Personalmente, rimuoverei il metodo Empty e sostituirei tutti gli usi di quel metodo con questo:

// let's say, that you have variables foo and pfoo - they are properly initialized.
Foo foo, *pfoo;

// replace line "foo.Empty()" with:
foo = Foo();

// replace line "pfoo->Empty()" with:
delete pfoo;
pfoo = new Foo();
// or
*pfoo = Foo();

Non vedo davvero alcun vantaggio nell'avere questo metodo vuoto. Nasconde ciò che sta realmente accadendo all'oggetto sulla strega che si chiama, neanche il nome è la scelta migliore.

Se il chiamante vuole davvero un oggetto pulito, non avrà problemi a costruire l'oggetto da solo.

Inoltre, considera di rendere immutabili gli oggetti, cioè , quando costruiti, non possono essere cambiati. Questo può in molti scenari salvarti dagli effetti collaterali imprevisti.

C'è qualcosa di ancora più comune di quello che hai suggerito. utilizzando swap.

Fondamentalmente fai qualcosa del genere:

T().swap(*this);

poiché molti contenitori standard (tutti i contenitori STL?) hanno un metodo di scambio di tempo costante, questo è un modo semplice e piacevole per svuotare un contenitore e assicurarsi che il suo deposito sia rilasciato.

Allo stesso modo, è un buon modo per "restringere per adattarsi" anche un contenitore, ma utilizzando il costruttore della copia anziché il costruttore predefinito.

considera l'utilizzo del posizionamento new :

void Empty() {
    this->~Foo();
    new (this) Foo();
}

Il tuo codice richiama operator = che potrebbe portare a tutti i tipi di effetti collaterali.

MODIFICA in risposta ai commenti. & # 8211; Questo codice è sicuramente ben definito, lo standard lo consente esplicitamente. Pubblicherò più avanti il ??paragrafo se trovo il tempo. Informazioni su delete & # 8211; ovviamente. Quello che intendevo era ~ Foo () , questa era una svista. E sì, anche Rob ha ragione; la distruzione dell'oggetto è effettivamente necessaria qui per chiamare il distruttore della stringa.

Questa può essere una potenziale fonte di perdita di memoria se si dispone di una memoria allocata dinamicamente nel costruttore.

Ecco come lo faccio:

class Foo {
private:
    int n_;
    std::string str_;
public:
    Foo() : n_(1234), str_("Hello, world!")
    {
    }

    void Empty()
    {
        Foo f;
        swap(f);
    }

    void swap(Foo & other) {
        std::swap(n_, other.n_);
        swap(str_, other.str_);
    }
};

void swap(Foo & one, Foo & other) {
    one.swap(other);
}

Metti la funzione di scambio nello stesso spazio dei nomi come la classe Foo. La ricerca dipendente dall'argomento lo trova quando gli utenti effettuano una chiamata per scambiare due Foo. Puoi implementare operator = anche usando la tua funzione di swap.

  

Questo sembra essere meglio della duplicazione del codice nel costruttore, ma mi chiedevo se * this = Foo () è un approccio comune quando si desidera cancellare un oggetto?

Cancellare un oggetto non è una cosa così comune da fare: più comunemente, un oggetto (forse anche un oggetto immutabile) è istanziato e contiene dati reali o non è istanziato.

Il tipo più comune di cose che vengono reimpostate sarebbero i container ... ma, ora non scriveresti le tue classi container,

  

Ci sono problemi con questa attesa di mordermi sul retro?

Si

  • Se questo non è in realtà un Foo ma è invece un DerivedFoo
  • Se l'operatore di assegnazione di Foo non esiste, o se è difettoso (ad esempio se non è definito e l'operatore predefinito non è buono, ad esempio perché un membro di dati è un puntatore nudo ).
  

Esistono altri modi migliori per ottenere questo genere di cose?

Sì, forse una funzione gratuita sarebbe migliore (eviterebbe entrambi i problemi di cui sopra):

template<class T> void reconstruct(T* p)
{
    p->~T();
    new (p) T();
}

Sì, questo non è efficiente in termini di prestazioni (creazione di un altro oggetto foo invece di lavorare sul posto) e ti morderà se allocassi la memoria nel costruttore con una cattiva perdita di memoria.

Renderlo più sicuro in termini di memoria sarebbe chiamare this- > delete e this = new foo () - ma sarà LENTO.

se vuoi essere superveloce crea un campo statico vuoto e memcpy in Reset.

se vuoi essere veloce assegna le proprietà una ad una.

se vuoi mantenere uno stile ragionevole senza duplicazione chiama Reset da Ctor come suggerito da Ates Goral ma perderai la costruzione più veloce con i parametri di default.

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