Usa oggetto nullo come argomento del metodo

3

Considera la seguente parte di codice

class Foo
    {
    public:
    //...
        bool valueFirstGet(int& value) const
            {
            if(this==nullptr)
                {return 0;}
            value=values[0];
            return 1;
            }
    //...

    private:
        int* values;
        size_t n_values;
    };

int main()
    {
    Foo* obj=findObject("key");
    int value;
    if(!obj->valueFirstGet(value))
        {printf("key does not exist\n");}

    return 0;
    }

findObject restituisce nullptr se non riesce a trovare l'oggetto. Va bene lasciare che la funzione membro esegua il controllo nullo invece del suo chiamante. Nel mio codice ci sono diverse chiamate a findObject direttamente seguito da una chiamata a valueFirstGet, quindi lasciare il controllo al chiamante rende il codice brutto.

EDIT:

Esiste un modo più semplice per evitare tutti i controlli nulli, oltre al fatto che findObject genera un'eccezione invece di restituire null?

EDIT 2:

Che dire di un wrapper statico?

    
posta user877329 07.01.2014 - 11:04
fonte

4 risposte

9

Il controllo null in valueFirstGet non può restituire true senza che ci sia un comportamento indefinito nel programma, perché è necessario dereferenziare un puntatore nullo (che causa UB) per lasciare che il test restituisca true. La cosa sgradevole del comportamento indefinito è che il comportamento di qualsiasi è completamente sanzionato e che il comportamento potrebbe cambiare tra le versioni del compilatore o anche i livelli di ottimizzazione senza preavviso.

Il modo corretto per fare ciò è avere il check in del chiamante o in un metodo di supporto. Se le chiamate findObject / valueFirstGet sono comunemente accoppiate senza ulteriore utilizzo per l'oggetto restituito da findObject , puoi anche fare qualcosa di simile:

bool tryValueFirstGet(const std::string& key, int& value)
{
    Foo* obj=findObject(key);
    return obj && obj->valueFirstGet(value);
}

Il comportamento di cortocircuito di && garantisce che valueFirstGet verrà chiamato solo se l'oggetto può essere trovato.

    
risposta data 07.01.2014 - 11:57
fonte
3

Un modo comune e utile per evitare il controllo del puntatore nullo è utilizzando il schema Oggetti Null . Essenzialmente, invece di usare la definizione predefinita del linguaggio di un oggetto nullo come "un puntatore nullo non è possibile effettuare il dereferenziazione", si crea la propria definizione con un comportamento nullo utile appropriato alla propria situazione. Ricorda, sei un programmatore. Non devi accontentarti di qualsiasi struttura limitata nella lingua.

L'utilizzo del modello di oggetto nullo per l'esempio potrebbe essere simile a questo:

class Foo {
  public:
    int getCount() {
      return n_values;
    }

    int firstValue() {
      return values[0];
    }
}

class NullFoo : public Foo {
  public:
    int getCount() {
      return 0;
    }

    int firstValue() {
      return 0; // Return a useful default if you want, or throw an exception if you want.
    }
}

Quindi se findObject non trova la chiave, crea un'istanza e restituisce un NullFoo (o restituisce un riferimento a un oggetto statico), che può essere dereferenziato senza comportamento indefinito e può fornire qualsiasi tipo di valore predefinito comportamento che ti piace, compreso il lancio di un'eccezione se la situazione lo richiede.

La cosa bella è che il tuo normale Foo può essere garantito per avere almeno un elemento, così puoi saltare anche molti controlli. Tutti i controlli vengono eseguiti al momento dell'istanziazione e il polimorfismo gestisce il resto.

    
risposta data 07.01.2014 - 18:16
fonte
0

Riepilogo:

Il problema di progettazione che hai presentato riguarda effettivamente due tipi di problemi, quindi coinvolge anche due classi di possibili soluzioni.

Is it ok to let the member function do the null check instead of its caller?

Semanticamente parlando, non ha senso. Chiamare un metodo di istanza di un oggetto nullo è logicamente scorretto perché l'oggetto non esiste ancora.

Detto questo, una soluzione più pulita a questo problema di progettazione sarebbe quella di gestire il caso nel modo più chiaro possibile utilizzando il meccanismo di eccezione disponibile nella lingua. Lascia che findObject lanci l'eccezione e lasci che la routine del chiamante lo gestisca come necessario. Ciò si traduce in un codice più leggibile.

In my code, there are several calls to findObject directly followed by a call to valueFirstGet so leaving the check to the caller makes the code ugly.

Questo è dove un metodo wrapper ti tornerà utile per evitare di ripetere te stesso.

    
risposta data 07.01.2014 - 21:40
fonte
-1

A volte, lavorando con grossi pezzi di codice legacy, tale controllo null si rivela utile. Ma non lo consiglierò mai per una classe scritta da zero.

Nota inoltre che in valueFirstGet() il valore & potrebbe anche essere null . Ho lavorato con il codice legacy, in cui anche queste situazioni dovevano essere prese in considerazione.

    
risposta data 08.01.2014 - 11:09
fonte

Leggi altre domande sui tag