Domanda

Sto lavorando su alcuni codice C ++ dove ho diversi oggetti Manager con metodi privati ??come

void NotifyFooUpdated();

che chiamare il metodo OnFooUpdated() sulle ascoltatori di questo oggetto.

Si noti che non modificano lo stato di questo oggetto, metodi const modo da potrebbero tecnicamente essere effettuate, anche se tipicamente modificano lo stato del sistema nel suo complesso. In particolare, gli oggetti listener potrebbero richiamare in questo oggetto e modificarlo.

Personalmente mi piacerebbe lasciare così come sono e non dichiararli const.

Tuttavia, i nostri codice statico checker bandiere QAC questo come una deviazione, quindi mi hanno o dichiararli const, o devo argomentare perché dovrebbero rimanere non edificati e ottenere una borsa di studio per la deviazione.

Quali sono argomenti per non dichiarare questi metodi const?
O devo seguire QAC e li dichiarare const?
Dovrei adottare un punto di vista strettamente locale è limitata a questo oggetto, o considerare il sistema nel suo complesso?

È stato utile?

Soluzione

In parole povere si dispone di una classe contenitore: Un manager pieno di osservatori. In C e C ++ si può avere contenitori const con valori non const. Considerate se è stato rimosso uno strato di avvolgimento:

list<Observer> someManager;

void NotifyFooUpdated(const list<Observer>& manager) { ... }

Si vedrebbe nulla di strano un NotifyFooUpdated globale di prendere una lista const, dal momento che non modifica la lista. Tale argomento const rende di fatto l'argomento di analisi più permissiva: La funzione accetta entrambe le liste const e non const. Tutto l'annotazione del const sui mezzi versione metodo di classe è const *this.

Per far fronte a un altro punto di vista:

  

Se non è possibile garantire che l'oggetto è stato richiamato la funzione sulla rimane la stessa prima e dopo la chiamata di funzione, di solito è lasciare che come non-const.

Questo è ragionevole solo se il chiamante ha l'unico riferimento all'oggetto. Se l'oggetto è globale (come nella domanda originale) o in un ambiente filettato, la costanza di ogni invito non garantisce lo stato dell'oggetto è invariato attraverso la chiamata. Una funzione senza effetti collaterali e che restituisce sempre lo stesso valore per gli stessi ingressi è puro . NotifyFooUpdate () non è chiaramente puro.

Altri suggerimenti

Se gli ascoltatori sono memorizzati come un insieme di puntatori è possibile chiamare un metodo non-const su di loro, anche se l'oggetto è const.

Se il contratto è che un ascoltatore può aggiornare il suo stato quando si riceve una notifica, allora il metodo dovrebbe essere non-const.

Si sta dicendo che l'ascoltatore possa richiamare nell'oggetto e modificarlo. Ma l'ascoltatore non cambierà per sé -. Così la chiamata Notifica potrebbe essere const, ma si passa un puntatore non-const per il proprio oggetto in esso

Se l'ascoltatore ha già tale puntatore (si ascolta solo una cosa), allora si può fare entrambi i metodi const, come l'oggetto sempre modificato è un effetto collaterale. Quello che sta accadendo è il seguente:

A chiama B modifica A e B in seguito.

Quindi, una chiamata porta B indirettamente alla propria modifica, ma non è una modifica diretta del sé.

Se questo è il caso, sia i metodi potrebbe e probabilmente dovrebbe essere const.

  

Quali sono argomenti per non dichiarare questi metodi const?
  O devo seguire QAC e li dichiarare const?
  Dovrei adottare un punto di vista strettamente locale è limitata a questo oggetto, o considerare il sistema nel suo complesso?

Ciò che si sa è che l'oggetto responsabile questo è stato chiamato per il fai non il cambiamento. Gli oggetti del manager poi invoca funzioni potrebbe modifica o non potrebbe. Tu non lo sai.

Dalla tua descrizione ho potuto immagino tale disegno una in cui tutti gli oggetti coinvolti sono const (e le notifiche possono essere trattati da loro scrivendo alla console). Se non si effettua questa funzione const, si vieta questo. Se si rendono const, si consente entrambi.

Credo che questo sia un argomento a favore di rendere questo const.

Se non è possibile garantire che l'oggetto che ha invocato la funzione rimane la stessa prima e dopo la chiamata di funzione, di solito è lasciare che come non-const. Pensateci - si potrebbe scrivere un ascoltatore, inserirla mentre l'oggetto è non-const, e quindi utilizzare questa funzione per violare const correttezza, perché una volta avuto accesso a tale oggetto quando era non edificati in passato. Questo è sbagliato.

Il mio prendere su di esso è che essi dovrebbero rimanere non const . Questo si basa sulla mia percezione che lo stato dell'oggetto direttore, è in realtà l'insieme degli stati di tutti gli oggetti che gestisce più l'eventuale stato di intrinseca cioè State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

Mentre l'incapsulamento nel codice sorgente può smentire questo rapporto di run-time. Sulla base della sua descrizione, credo che questo stato aggregato è riflettente del comportamento in fase di esecuzione del programma.

Per rafforzare la mia tesi:. Affermo che il codice dovrebbe sforzarsi di riflettere al tempo di esecuzione dei programmi di comportamento a preferenza di stretta aderenza alle esatte semantica di compilazione

a const , o di non const :. Questo è il problema

Argomenti per const:

  • I metodi in questione non modificare lo stato dell'oggetto.
  • I tuoi statici bandiere codice checker la mancanza di const come una deviazione, forse si dovrebbe ascoltare.

Argomenti contro const:

  • I metodi modificare lo stato del sistema nel suo complesso.
  • Listener oggetti mia modificare l'oggetto.

Personalmente mi piacerebbe andare con lasciandola come const, il fatto che potrebbe modificare lo stato del sistema nel suo complesso è più o meno come un riferimento di puntatore nullo. E 'un metodo const, non modifica l'oggetto in questione, ma sarà in crash il programma modificando in tal modo lo stato di tutto il sistema.

Ci sono alcuni argomenti buoni per contro const , ecco ecco il mio prendere: -

Personalmente, mi piacerebbe non hanno questi "OnXXXUpdated" come parte delle mie classi di gestione. Credo che questo sia il motivo per cui v'è una certa confusione su best-practice. Stai notificare le parti interessate su qualcosa, e non so se lo stato dell'oggetto sta per cambiare nel corso del processo di notifica. Si può, o non può. Ciò che è ovvio per me, è che il processo di notifica parti interessate dovrebbe essere un const.

Quindi, per risolvere questo dilemma, questo è quello che vorrei fare:

sbarazzarsi delle funzioni OnXXXXUpdated dalle classi gestore.

Scrivi una Gestione notifiche, ecco un prototipo, con le seguenti ipotesi:

"Args" è una classe base arbitraria per il passaggio di informazioni quando le notifiche avvengono

"delegato" è una sorta di un puntatore alla funzione (ad esempio FastDelegate).

class Args
{
};

class NotificationManager
{
private:
    class NotifyEntry
    {
    private:
        std::list<Delegate> m_Delegates;

    public:
        NotifyEntry(){};
        void raise(const Args& _args) const
        {
            for(std::list<Delegate>::const_iterator cit(m_Delegates.begin());
                cit != m_Delegates.end();
                ++cit)
                (*cit)(_args);
        };

        NotifyEntry& operator += (Delegate _delegate) {m_Delegates.push_back(_delegate); return(*this); };
    }; // eo class NotifyEntry

    std::map<std::string, NotifyEntry*> m_Entries;

public:
    // ctor, dtor, etc....

    // methods
    void register(const std::string& _name);     // register a notification ...
    void unRegister(const std::string& _name);   // unregister it ...

    // Notify interested parties
    void notify(const std::string& _name, const Args& _args) const
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
           cit.second->raise(_args);
    }; // eo notify

    // Tell the manager we're interested in an event
    void listenFor(const std::string& _name, Delegate _delegate)
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
            (*cit.second) += _delegate;
    }; // eo listenFor
}; // eo class NotifyManager

ho lasciato un po 'di codice come si può probabilmente dire, ma si ottiene l'idea. Immagino che questo Gestione notifiche sarebbe un Singleton. Ora, assicurando che il Gestore di notifica è creato nella fase iniziale, il resto dei vostri manager sufficiente registrarsi loro notifiche nella loro costruttore in questo modo:

MyManager::MyManager()
{
    NotificationMananger.getSingleton().register("OnABCUpdated");
    NotificationMananger.getSingleton().register("OnXYZUpdated");
};


AnotherManager::AnotherManager()
{
    NotificationManager.getSingleton().register("TheFoxIsInTheHenHouse");
};

Ora, quando il manager ha bisogno di informare gli interessati, semplicemente chiamate notifica:

MyManager::someFunction()
{
    CustomArgs args; // custom arguments derived from Args
    NotificationManager::getSingleton().notify("OnABCUpdated", args);
};

Altre classi possono ascoltare questa roba.

Ho capito che ho appena scritto il pattern Observer, ma la mia intenzione era di mostrare che il problema è nel modo in cui queste cose vengono sollevate e se sono in un const-stato o meno. In astratto il processo di comunicazione fuori della classe mananager, destinatari della notifica sono liberi di modificare quella classe dirigente. Basta che non il manager di notifica. Penso che questo sia giusto.

Inoltre, avendo un unico luogo alle notifiche sollevare è buono pracice imho, in quanto ti dà un unico luogo dove è possibile rintracciare le notifiche.

Credo che si stanno seguendo HICPP o qualcosa di simile.

Quello che facciamo è se il nostro codice viola QACPP e pensiamo che è in errore, allora notiamo via Doxygen (tramite comando AggiungiAlGruppo in modo da ottenere una lista di loro facilmente), dare una ragione jusifying per cui stiamo violando e poi disattivare l'avviso tramite il comando //PRQA.

  

Si noti che non modificano lo stato   di questo oggetto, così che potevano   Metodi const tecnicamente essere effettuati,   anche se tipicamente modificano la   stato del sistema nel suo complesso. Nel   particolare, l'ascoltatore oggetti potrebbero   richiamare in questo oggetto e modificare   esso.

Dal momento che l'ascoltatore può cambiare uno stato, allora questo metodo non dovrebbe essere const. Da quello che hai scritto, sembra che si sta utilizzando un sacco di const_cast e chiamata tramite puntatori.

const correttezza ha un modo (volutamente auspicabile) di propagazione. si dovrebbe usare const ovunque si può farla franca, mentre const_cast e stile C-cast dovrebbero essere artefatti di trattare con codice client -. mai nel codice, ma molto molto rare eccezioni

se chiamate void NotifyFooUpdated(); listeners[all].OnFooUpdated() mentre su OnFooUpdated() non è const, allora si dovrebbe beneficiare in modo esplicito questa mutazione. se il codice è corretto const in tutto (che sto mettendo in discussione), poi ne fanno esplicita (tramite il metodo dichiarazione / accesso all'ascoltatore) che si stanno mutando le ascoltatori (membri), quindi NotifyFooUpdated() dovrebbe qualificarsi come non-const. così, basta dichiarare la mutazione più vicino possibile alla fonte possibile e si dovrebbe verificare e const correttezza si propagano correttamente.

Fare funzioni virtuali const è sempre una decisione difficile. Rendendoli non-const è la facile via d'uscita. Una funzione listener dovrebbe essere const, in molti casi: se non cambia l'aspetto di ascolto (per questo oggetto). Se l'ascolto di un evento causerebbe il partito di ascolto per annullare la registrazione stesso (come un caso generale), allora questa funzione dovrebbe essere non-const.

Anche se lo stato interno dell'oggetto può cambiare in una chiamata OnFooChanged, a livello dell'interfaccia, la prossima volta OnFooChanged è chiamato, avrà un risultato simile. Che lo rende const.

Quando si utilizza const nelle classi stai aiutando gli utenti di tale conoscenza di classe come la classe sarà interagire con i dati. Stai facendo un contratto. Quando si dispone di un riferimento a un oggetto const, si sa che tutte le chiamate effettuate su tale oggetto non cambierà il suo stato. La costanza di tale rinvio è ancora solo un contratto con il chiamante. L'oggetto è ancora libero di fare alcune azioni non-const in background utilizzando le variabili mutabili. Ciò è particolarmente utile quando il caching delle informazioni.

Per esempio si può avere classe con il metodo:

int expensiveOperation() const
{
    if (!mPerformedFetch)
    {
        mValueCache = fetchExpensiveValue();
        mPerformedFetch = true;
    }
    return mValueCache;
}

Questo metodo può richiedere molto tempo per eseguire la prima volta, ma sarà memorizzare nella cache il risultato per le chiamate successive. Hai solo bisogno di assicurarsi che il file di intestazione dichiara le variabili performedFetch e valueCache come mutevole.

class X
{
public:
    int expensiveOperation() const;
private:
    int fetchExpensiveValue() const;

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

In questo modo un oggetto const fare il contratto con il chiamante, e agire come è const mentre si lavora un po 'più intelligente in background.

Vorrei suggerire facendo la classe dichiara l'elenco degli ascoltatori come mutabile, e fare tutto il resto come const possibile. Per quanto riguarda il chiamante dell'oggetto è interessato, l'oggetto è ancora const e di agire in questo modo.

Const intende lo stato dell'oggetto non è modificato dalla funzione membro , né più né meno. Non ha nulla a che fare con gli effetti collaterali. Quindi, se ho capito il tuo caso in modo corretto, lo stato dell'oggetto non cambia che significa che la funzione deve essere dichiarata const, lo stato delle altre parti della vostra applicazione non ha nulla a che fare con questo oggetto. Anche quando talvolta stato oggetto ha non const sub-oggetti, che non fanno parte dello stato logico dell'oggetto (ad esempio mutex), le funzioni devono ancora essere fatto const, e quelle parti devono essere dichiarate mutevole.

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