La regola del 5 - usarlo o no?

20

La regola di 3 ( la regola di 5 nel nuovo standard c ++) afferma:

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

Ma, d'altra parte, " Pulisci codice" "di Martin consiglia di rimuovere tutti i costruttori e i distruttori vuoti (pagina 293, G12: Clutter ):

Of what use is a default constructor with no implementation? All it serves to do is clutter up the code with meaningless artifacts.

Quindi, come gestire queste due opinioni opposte? Dovrebbero davvero essere implementati costruttori / distruttori vuoti?

Il prossimo esempio dimostra esattamente cosa intendo:

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

Compila bene usando g ++ 4.6.1 con:

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

Il distruttore per struct A è vuoto e non realmente necessario. Quindi, dovrebbe essere lì, o dovrebbe essere rimosso?

    
posta BЈовић 12.04.2012 - 13:01
fonte

6 risposte

43

Per iniziare, la regola dice "probabilmente", quindi non si applica sempre.

Il secondo punto che vedo qui è che se devi dichiarare uno dei tre, è perché sta facendo qualcosa di speciale come allocare memoria. In questo caso, gli altri non sarebbero vuoti poiché dovevano gestire lo stesso compito (come copiare il contenuto della memoria allocata dinamicamente nel costruttore di copie o liberare tale memoria).

Quindi, come conclusione, non dovresti dichiarare costruttori o distruttori vuoti, ma è molto probabile che se ne è necessario uno anche gli altri sono necessari.

Come per il tuo esempio: in tal caso, puoi lasciare fuori il distruttore. Non fa niente, ovviamente. L'uso di puntatori intelligenti è un perfetto esempio di dove e perché la regola del 3 non regge.

È solo una guida su dove dare una seconda occhiata al tuo codice nel caso in cui potresti aver dimenticato di implementare funzionalità importanti che altrimenti avresti perso.

    
risposta data 12.04.2012 - 13:20
fonte
4

Qui non c'è davvero alcuna contraddizione. La regola di 3 parla del distruttore, del costruttore di copie e dell'operatore di assegnazione delle copie. Uncle Bob parla dei costruttori predefiniti vuoti.

Se hai bisogno di un distruttore, la tua classe probabilmente contiene dei puntatori alla memoria allocata dinamicamente e probabilmente vorrai avere un copione e un operator=() che faccia una copia profonda. Questo è completamente ortogonale al fatto che tu abbia o meno bisogno di un costruttore predefinito.

Si noti anche che in C ++ ci sono situazioni in cui è necessario un costruttore predefinito, anche se è vuoto. Supponiamo che la tua classe abbia un costruttore non predefinito. In tal caso, il compilatore non genererà per te un costruttore predefinito. Ciò significa che gli oggetti di questa classe non possono essere archiviati in contenitori STL, perché questi contenitori si aspettano che gli oggetti siano costruttivi di default.

D'altra parte, se non hai intenzione di mettere gli oggetti della tua classe in contenitori STL, un costruttore predefinito vuoto è certamente una confusione inutile.

    
risposta data 12.04.2012 - 16:45
fonte
2

Qui il tuo potenziale (*) equivalente a un costruttore / assegnatore / distruttore di default ha uno scopo: documenta il fatto che hai per quanto riguarda il problema e hai stabilito che il comportamento predefinito era corretto. BTW, in C ++ 11, le cose non si sono stabilizzate abbastanza per sapere se =default può servire a questo scopo.

(Esiste un altro potenziale scopo: fornire una definizione fuori linea anziché quella in linea predefinita, meglio documentarla in modo esplicito se si ha motivo di farlo).

(*) Potenziale perché non ricordo un caso di vita reale in cui la regola dei tre non si applicava, se dovevo fare qualcosa in uno, dovevo fare qualcosa negli altri.

Modifica dopo l'aggiunta di un esempio. il tuo esempio usando auto_ptr è interessante. Stai utilizzando un puntatore intelligente, ma non uno che è all'altezza del lavoro. Preferisco scriverne uno che - specialmente se la situazione si verifica spesso - piuttosto che fare ciò che hai fatto. (Se non sbaglio, né lo standard né il boost ne forniscono uno).

    
risposta data 12.04.2012 - 13:26
fonte
1

La regola del 5 è un'estensione cautelativa della regola del 3 che è un comportamento prudente contro l'eventuale abuso di oggetti.

Se hai bisogno di avere un distruttore, significa che hai fatto un po 'di "gestione delle risorse" diversa da quella predefinita (solo costruire e distruggere valori ).

Poiché copia, assegna, sposta e trasferisci di default copia valori , se non stai tenendo solo valori , devi definire cosa fare.

Detto questo, il C ++ cancella la copia se definisci lo spostamento e cancella lo spostamento se definisci la copia. Nella maggior parte dei casi è necessario definire se si desidera emulare un valore (quindi copiare mut clone della risorsa e move non ha senso) o un gestore risorse (e quindi spostare la risorsa, dove la copia non ha senso: la regola di 3 diventa la regola di altri 3 )

I casi in cui devi definire sia la copia che lo spostamento (regola di 5) sono piuttosto rari: in genere hai un "grande valore" che deve essere copiato se assegnato a oggetti distinti, ma può essere spostato se preso da un temporaneo oggetto (evitando un clone quindi distruggi ). Questo è il caso dei contenitori STL o dei contenitori aritmetici.

Un caso può essere matrici: devono supportare la copia perché sono valori, ( a=b; c=b; a*=2; b*=3; non deve influenzarsi a vicenda) ma possono essere ottimizzati supportando anche lo spostamento ( a = 3*b+4*c ha un + che richiede due provvisori e genera un temporaneo: evitare clonare ed eliminare può essere utile)

    
risposta data 13.04.2012 - 09:51
fonte
1

Preferisco un fraseggio diverso della regola dei tre, che sembra più ragionevole, che è "se la tua classe ha bisogno di un distruttore (diverso da un distruttore virtuale vuoto) probabilmente ha bisogno anche di un costruttore di copia e di un operatore di assegnazione."

Specificarlo come una relazione unidirezionale dal distruttore rende alcune cose più chiare:

  1. Non si applica nei casi in cui fornisci un costruttore di copia non predefinito o un operatore di assegnazione solo come ottimizzazione.

  2. Il motivo della regola è che il costruttore di copie o l'operatore di assegnazione predefinito può rovinare la gestione manuale delle risorse. Se gestisci manualmente le risorse, è probabile che ti sia reso conto che avrai bisogno di un distruttore per rilasciarle.

risposta data 25.12.2014 - 21:20
fonte
-3

C'è un altro punto non menzionato nella discussione: un distruttore dovrebbe sempre essere virtuale.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

Il costruttore deve essere dichiarato come virtuale nella classe base per renderlo virtuale anche in tutte le classi derivate. Quindi, anche se la tua classe base non ha bisogno di un distruttore, finisci per dichiarare e implementare un distruttore vuoto.

Se metti tutti gli avvertimenti su (-Wall -Wextra -Weffc ++) g ++ ti avviserà di questo. Ritengo una buona pratica dichiarare sempre un distruttore virtuale in qualsiasi classe, perché non si sa mai se la classe diventerà una classe base. Se il distruttore virtuale non è necessario, non fa male. Se lo è, risparmi tempo per trovare l'errore.

    
risposta data 14.04.2012 - 09:01
fonte

Leggi altre domande sui tag