Domanda

Ho una lezione CContainer che ha alcuni membri CMemberX, CMemberY, che sono indipendenti l'uno dall'altro e dall'altro CClientA, CClientB classi che utilizzano CContainer.

#include "MemberX.h"
#include "MemberY.h"

class CContainer
{
public:
    CMemberX & GetX() const { return m_x; }
    CMemberY & GetY() const { return m_y; }

private:
    CMemberX m_x;
    CMemberY m_y;
};

Voglio evitare di dover ricompilare tutto CClient classi quando si modifica una delle classi CMember classi che utilizzano dichiarazioni anticipate e allocazione dinamica di m_x E m_y.

Inizialmente, ho creato i suggerimenti per i membri:

// Container.h
class CMemberX;
class CMemberY;

class CContainer
{
public:
    CContainer();
    ~CContainer();

    CMemberX & GetX() const { ASSERT(m_pX != NULL); return *m_pX; }
    CMemberY & GetY() const { ASSERT(m_pY != NULL); return *m_pY; }

private:
    CMemberX* m_pX;
    CMemberY* m_pY;
};

// Container.cpp
#include "Container.h"
#include "MemberX.h"
#include "MemberY.h"

// Allocate members on heap
CContainer::CContainer() : m_pX(new CMemberX()), m_pY(new CMemberY()) {}
CContainer::~CContainer() { delete m_pX; delete m_pY; }

Poi ho pensato che avrei potuto anche usare i riferimenti invece dei puntatori, quindi assomiglia di più al codice originale:

// Container.h
class CMemberX;
class CMemberY;

class CContainer
{
public:
    CContainer();
    ~CContainer();

    CMemberX & GetX() const { return m_x; }
    CMemberY & GetY() const { return m_y; }

private:
    CMemberX & m_x;
    CMemberY & m_y;
};

// Container.cpp
#include "Container.h"
#include "MemberX.h"
#include "MemberY.h"

// Allocate members on heap
CContainer::CContainer() : m_x(*new CMemberX()), m_y(*new CMemberY()) {}
CContainer::~CContainer() { delete &m_x; delete &m_y; }

Quello che non mi piace dei membri del puntatore è che esso sembra come potrebbero essere i puntatori NULL oppure gli oggetti vengono sostituiti in fase di esecuzione, il che non è il caso.

Quello che non mi piace dei riferimenti è che il codice in CTor e DTor sembra un po' confuso.

Quale approccio è preferibile?C'è una soluzione migliore?

Nota relativa alla copia/assegnazione: Istanze del CContainer classe non verranno in nessun caso copiate o assegnate l'una all'altra.

È stato utile?

Soluzione

Credo che sia quello che le variabili const sono per:

CMember * const m_x;

Impossibile modificare m_x dopo l'inizializzazione ...

Altri suggerimenti

Penso che sia un po' sorprendente utilizzare un riferimento quando esiste la semantica della proprietà.No necessariamente la rendono una cattiva idea, tutto sommato, ma pesa contro.

Penso di aver utilizzato i riferimenti come membri solo nei casi in cui entrambi:

  • al costruttore viene fornito un oggetto, necessario per sopravvivere a questo oggetto.
  • l'assegnazione è comunque vietata.

Quindi, ad esempio, potrebbero essere adatte le dipendenze iniettate come gli oggetti factory o service.Al contrario, in C++ spesso preferiresti inserire dipendenze con parametri di modello anziché oggetti, quindi il problema potrebbe non sorgere.

Trovo anche che più a lungo utilizzo il C++, più desidero che i tipi siano assegnabili, a meno che non ci sia una buona ragione per non esserlo.Il solito trucco per ridurre le dipendenze in fase di compilazione nel modo desiderato è "Pimpl", non "Rimpl", per un motivo.Passando da un membro oggetto a un membro di riferimento, stai rendendo la tua classe non copiabile per impostazione predefinita, dove in precedenza forse era copiabile.Questo dettaglio di implementazione non dovrebbe limitare l'interfaccia della classe.Con Pimpl puoi implementare in modo pulito l'assegnazione e lo scambio.Con questi riferimenti dovresti assegnare o scambiare entrambi i membri.Se il secondo scambio fallisce, hai perso la forte garanzia di eccezione:anche se le classi CMemberX e CMemberY hanno assegnazioni e scambi senza errori, ciò non ha importanza.

Quindi non penso che mi piaccia il riferimento in questo caso, ma non ho visto il resto del tuo codice.Forse c'è qualche motivo per cui non si applica nessuna delle preoccupazioni sull'assegnazione: ad esempio se CContainer è esso stesso una classe RAII, in genere le uniche operazioni del ciclo di vita che dovrebbe supportare sono la costruzione e la distruzione.

Ci sono stati un sacco di domande qui circa l'opportunità di utilizzare i riferimenti come membri (ad esempio nel caso io preferisco puntatori o riferimenti a dati membro ), e mi sembra che l'opinione della maggioranza (il che avviene anche per essere il mio) è -? non lo fanno. Se non si desidera che i puntatori da modificare renderli const -. Non posso vedere come, dato il codice, che possono eventualmente essere NULL

L'allocazione dinamica non fa nulla per voi in relazione a ciò che si vuole. Non dover ricompilare CClient e CContainer

L'uso consentito solo quando si utilizza l'ora dichiarazioni sta dichiarando puntatore (s) per il tipo forward-dichiarato.

Non appena si utilizza un metodo o un membro del tipo forward-dichiarata non si compila: il compilatore deve conoscere il tipo completo di tutto ciò che sta usando

.

In breve: o non avete mai ricompilare [si dichiara solo i puntatori al tipo dichiarato in avanti] o si è sempre ricompilare, nel caso in cui in realtà utilizzati CContainer

.

Steve Jessop già accennato l'idioma Pimpl di passaggio, ma penso che si dovrebbe controllare questo fuori se non l'hai già incontrato esso: Compilation Firewalls

Nel secondo blocco di codice nella tua domanda hai puntatori membro private, che vengono inizializzati e distrutto insieme con la classe genitore. Queste informazioni dovrebbero essere sufficienti per il lettore del codice a capire cosa sta succedendo.

In aggiunta si potrebbe dichiarare il const puntatori: CMember* const m_pX; per indicare che essi non possono essere modificate dopo l'inizializzazione. Ora il compilatore prenderà modifiche accidentali.

Non sei veramente te stesso comprando nulla.
(Leggermente più corto tempo di compilazione in situazioni limitato).

Ma si sta ammasserai un sacco di altro codice che deve essere mantenuto sul vostro piatto.

Se questi oggetti sono membri naturali poi li lasciano in qualità di membri.
Con la creazione di loro nello stack e la loro memorizzazione come puntatori o riferimenti si deve chiedere un sacco di domande appiccicose che hanno bisogno di codice per rispondere a loro.

  • Che cosa accade quando si copia costruire l'oggetto.
    • avrei normalmente si aspetta l'obejct e tutti i suoi membri da copiare.
      Usando puntatori o riferimenti che si sta per avere a che fare lavoro extra per replicare questa funzionalità che è già previsto.
  • Che cosa accade quando si assegna gli oggetti.
    • I riferimenti non funziona (anche se si ottiene intorno a questo utilizzando i riferimenti Boost).
      Ma avete ancora il problema con i membri in attesa da copiare.
  • Che cosa accade quando si elimina l'oggetto.
    • Se è stato implementato tutto quanto sopra in modo corretto di fare copie bene.
      In caso contrario, è necessario iniziare a pensare a puntatori condivisi per implementare la funzionalità necessaria.

Per come stanno le versioni 2 e 3 (del codice in questione) sono gravemente lacunosa e l'unica versione che funziona davvero è 1.

Nella mia mente il semplice fatto che i costi di manutenzione saranno così molto più bassi con la versione 1, che raccomandando una di versione 2 o 3 è controproducente. Il tempo supplementare per compilare un altro classe quando un membro viene modificato è relativamente piccolo in confronto alla complessità si sta aggiungendo al codice.

Inoltre si parla di qualcun altro commento s che il codice non è così pulito come quello descritto sopra. Questo solo sottolinea il mio punto che questo è un male ottimizzazione che renderà più difficile ottenere la classe di funzionare correttamente e tenerlo mantenuto in quello stato.

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