Memorizzazione di un puntatore su un argomento passato da riferimento (non costante)

3

Quando si progetta un'interfaccia per il passaggio di oggetti che devono essere memorizzati per un uso successivo e che non devono essere "null", sono sempre un po 'incerto se l'argomento debba essere passato per riferimento o come puntatore.

Questo è un esempio di cosa intendo:

class Foo
{
private:
    Bar* m_bar;

public:
    Foo() : m_bar(nullptr)
    {
    }

    void Register(Bar& bar)
    {
        m_bar = &bar;
        m_bar->Registered();
    }
    // -- OR --
    void Register(Bar* const bar)
    {
        if (bar == nullptr)
        {
            // Error!
        }

        m_bar = bar;
        m_bar->Registered();
    }

    // Some method makes use of the stored pointer later
    void DoSomething()
    {
        if (m_bar == nullptr)
        {
            // Error!
        }

        m_bar->DoOtherThing();
    }
};

I miei pensieri su questo sono:

  1. Il riferimento passato può uscire dall'ambito prima che venga chiamato DoSomething , ma ciò può accadere anche con l'oggetto puntato.
  2. Usando la versione pass-by-non-const-reference si elimina la duplicazione del controllo per null e dice al chiamante che non è possibile registrare 'nothing'.
  3. Sarebbe meglio passare un riferimento nel costruttore di Foo come richiesto da DoSomething , ma a volte questa non è un'opzione.
  4. Sarebbe meglio passare direttamente un riferimento a DoSomething , ma ancora, questo non è sempre possibile.

Quindi, se ho bisogno di quel tipo di metodo setter / registro separato, sarebbe più chiaro usare un riferimento o un puntatore?

PS So che ci sono due domande molto simili Memorizzazione di un parametro pass-by-reference come puntatore - Cattiva pratica? e Riferimento vs puntatori di riferimento negli argomenti C ++ / C , ma penso che entrambi siano interessati a problemi leggermente diversi. Il primo si occupa principalmente dell'uso (ab) di const e quest'ultimo non dice nulla riguardo a memorizzare un puntatore. Potrebbe esserci un'altra domanda / risposta là fuori, se è così, per favore contrassegna semplicemente un duplicato!

    
posta Simon Lehmann 19.09.2014 - 18:53
fonte

4 risposte

2

Preparando la mia tuta ignifuga, poiché ritengo che qualche pregiudizio debba essere presente in ogni risposta a questa domanda.

In primo luogo, vorrei sottolineare che lavoro all'interno del mondo embedded, quindi RAII e l'allocazione degli stack sono generalmente la strada da percorrere. In quasi tutte le circostanze, vedo passare qualcosa per riferimento come una buona idea a causa dell'ovvia ragione: non può essere NULL. Ma in particolare, questo è perché principalmente impongo l'allocazione - quindi di solito non prendo qualcosa dall'heap e lo riempio in un riferimento quando lo passo. Questo stile di programmazione elimina le fonti comuni di problemi per le preoccupazioni 1, 3, e 4 che hai elencato.

Se passo un puntatore, è per una di queste due ragioni: 1) NULL è davvero un'opzione (un po 'rara) o 2) Voglio segnalare che il mio oggetto sta assumendo la proprietà di quello che è passato. I rendersi conto che questo è un po 'per stile e dominio, ma è una regola empirica che mi ha aiutato a fare meno errori e comunicare con i programmatori del mio team su cosa sta facendo il mio codice.

    
risposta data 20.09.2014 - 04:37
fonte
0

In una situazione tipica , si passa un puntatore solo se si desidera accettare nullptr o l'utilizzo tipico del tipo non è un valore. Questo è, per esempio, alcune (ma non tutte!) Interfacce. Nella maggior parte dei casi si prende sempre un riferimento (potenzialmente const ).

    
risposta data 19.09.2014 - 23:34
fonte
0

Il problema nella mia mente è che se si utilizza un riferimento o un puntatore, la classe ha una vista in dati che viene creata separatamente, può essere distrutta separatamente, e la classe non ha modo di saperlo.

Foo f;
if (something) {
    Bar x;
    // stuff with x
    f.Register(x);
}

f.DoSomething(); // screwed! 

Non è un buon segno che un codice così innocente faccia qualcosa di così brutto. Dipende dai dettagli del tuo intento in cui non sei entrato, ma probabilmente userei solo un puntatore condiviso. Un'alternativa sarebbe avere il distruttore di Bar raggiungere in Foo e rendere il puntatore nullo, ma ora Bar ha bisogno di un puntatore a Foo e sta diventando un po 'sciocco. Se ritieni che un indicatore condiviso non significhi le tue esigenze come alternativa a entrambe le soluzioni che hai preso in considerazione, puoi spiegare perché?

Modifica: vorrei aggiungere brevemente che se in una situazione simile a quella descritta sopra, non si desidera estendere la durata come fa un puntatore condiviso, ma semplicemente per verificare la validità e agire in modo appropriato, si potrebbe usare un puntatore debole , ma sta iniziando a sembrare eccessivo.

    
risposta data 21.09.2014 - 19:01
fonte
-2

La nostra guida alla codifica - scritta in parte da me :-) - dice di passare sempre da un puntatore in questo caso in cui vuoi tenere premuto un puntatore.

  1. Con i puntatori, puoi utilizzare le versioni const e non const , qualunque sia il più appropriato (ad esempio, puoi solo mantenere un const Bar* , nel qual caso puoi anche solo passare a const Bar* ) L'utilizzo di un const Bar& come tipo di parametro è associato per provocare un arresto di runtime più presto che dopo, perché:

    • un const-const lega - silenziosamente - ai provvisori: tenere i provvisori è ovviamente una cattiva idea
    • tra le altre cose, qualsiasi conversione di tipo implicita crea una temporanea, quindi questo ti morderà anche quando passi un lvalue che è di un tipo implicitamente convertibile.
  2. Se si dichiara di passare sempre dei puntatori, nessuno otterrà idee divertenti e tenterà di mantenere un riferimento const . (vedi il punto sopra)

  3. Il lato client potrebbe essere leggermente più consapevole dei potenziali problemi di durata quando l'interfaccia coinvolge un puntatore, anche se il puntatore non è sempre visibile sintatticamente. Cioè, quando vedo un'interfaccia Register(Bar*) parlando personalmente , sarei meno sorpreso se si tiene sul puntatore che se vedo un Register(Bar&)

  4. Che "non dovrebbe essere nulla", IMHO, non importa troppo per questa decisione. Passare un puntatore significa che la funzione può effettivamente controllarlo, ma anche in questo caso può essere appropriato o meno. (Forse è solo una violazione di precondizione (normalmente non verificata).

risposta data 19.09.2014 - 23:13
fonte

Leggi altre domande sui tag