2009-04-17 11 views
12

Ho un oggetto (per me) complesso con circa 20 membri dati, molti dei quali puntano ad altre classi. Quindi, per il costruttore, ho una lunga e complessa lista di inizializzazione. La classe ha anche una dozzina di diversi costruttori, che riflettono i vari modi in cui la classe può essere creata. La maggior parte di questi articoli inizializzati sono invariati tra ciascuno di questi diversi costruttori.Gestione di una classe con un lungo elenco di inizializzazione e più costruttori?

La mia preoccupazione è che ora ho un grosso pezzo di codice copiato (o per lo più copiato) che, se ho bisogno di aggiungere un nuovo membro alla classe, non può farlo in ciascuno degli elenchi di inizializzazione del costruttore.

class Object 
{ 
    Object(); 
    Object(const string &Name); 
    Object (const string &Name, const string &path); 
    Object (const string &Name, const bool loadMetadata); 
    Object (const string &Name, const string &path, const bool loadMetadata); 
} 

Object::Object() : 
    name(), 
    parent_index (0), 
    rowData (new MemoryRow()), 
    objectFile(), 
    rows (new MemoryColumn (object_constants::RowName, OBJECTID, object_constants::ROWS_OID)), 
    cols (new MemoryColumn (object_constants::ColName, OBJECTID, object_constants::COLS_OID)), 
    objectName (new MemoryColumn(object_constants::ObjName, STRING, object_constants::short_name_len, object_constants::OBJECTNAME_OID)), 
    parent  (new MemoryColumn(object_constants::ParentName, STRING, object_constants::long_name_len, object_constants::PARENT_OID)), 
    parentIndex (new MemoryColumn(object_constants::ParentIndex, OBJECTID, object_constants::PARENTINDEX_OID)), 
    childCount (new MemoryColumn (object_constants::ChildCount, INTEGER, object_constants::CHILD_COUNT_OID)), 
    childList (new MemoryColumn (object_constants::ChildList, STRING, object_constants::long_name_len, object_constants::CHILD_OID)), 
    columnNames (new MemoryColumn (object_constants::ColumnNames, STRING, object_constats::short_name_len, object_constants::COLUMN_NAME)), 
    columnTypes (new MemoryColumn (object_constants::ColumnTypes, INTEGER, object_constants::COLUMN_TYPE)), 
    columnSizes (new MemoryColumn (object_constants::ColumnSizes, INTEGER, object_constants::COLUMN_SIZE)) 
{} 

Quindi ripetere come sopra per gli altri costruttori. C'è un modo intelligente di utilizzare il costruttore predefinito per questo, quindi modificare i risultati per gli altri costruttori?

risposta

13

E riguardo il refactoring dei campi comuni in una classe base. Il costruttore predefinito per la classe base gestirà l'inizializzazione per la pletora di campi predefiniti. Sarebbe simile a questa:

class BaseClass { 
    public: 
    BaseClass(); 
}; 

class Object : public BaseClass 
{ 
    Object(); 
    Object(const string &Name); 
    Object (const string &Name, const string &path); 
    Object (const string &Name, const bool loadMetadata); 
    Object (const string &Name, const string &path, const bool loadMetadata); 
}; 

BaseClass::BaseClass() : 
    parent_index (0), 
    rowData (new MemoryRow()), 
    objectFile(), 
    rows (new MemoryColumn (object_constants::RowName, OBJECTID, object_constants::ROWS_OID)), 
    cols (new MemoryColumn (object_constants::ColName, OBJECTID, object_constants::COLS_OID)), 
    objectName (new MemoryColumn(object_constants::ObjName, STRING, object_constants::short_name_len, object_constants::OBJECTNAME_OID)), 
    parent  (new MemoryColumn(object_constants::ParentName, STRING, object_constants::long_name_len, object_constants::PARENT_OID)), 
    parentIndex (new MemoryColumn(object_constants::ParentIndex, OBJECTID, object_constants::PARENTINDEX_OID)), 
    childCount (new MemoryColumn (object_constants::ChildCount, INTEGER, object_constants::CHILD_COUNT_OID)), 
    childList (new MemoryColumn (object_constants::ChildList, STRING, object_constants::long_name_len, object_constants::CHILD_OID)), 
    columnNames (new MemoryColumn (object_constants::ColumnNames, STRING, object_constats::short_name_len, object_constants::COLUMN_NAME)), 
    columnTypes (new MemoryColumn (object_constants::ColumnTypes, INTEGER, object_constants::COLUMN_TYPE)), 
    columnSizes (new MemoryColumn (object_constants::ColumnSizes, INTEGER, object_constants::COLUMN_SIZE)) 
{} 

I suoi costruttori oggetto dovrebbe guardare un po 'più gestibile, ora:

Object::Object() : BaseClass() {} 
Object::Object (const string &Name): BaseClass(), name(Name) {} 
Object::Object (const string &Name, const string &path): BaseClass(), name(Name), path_(path){} 
Object::Object (const string &Name, const bool loadMetadata): BaseClass(), name(Name){} 
Object::Object (const string &Name, const string &path, const bool loadMetadata): BaseClass(), path_(path) {} 

simile in natura per la risposta di iraimbilanja, ma evita l'aggiunta di un interno di classe per l'accesso ai dati, che potrebbe influenzare un sacco di codice esistente. Se hai già una gerarchia di classi, tuttavia, potrebbe essere difficile includerla in una classe base.

+3

potrebbe voler utilizzare l'ereditarietà privata perché questo è un dettaglio di implementazione. –

+0

o mettere la baseclass in uno spazio dei nomi vuoto? – jiggunjer

4

Boost::Parameter semplifica l'implementazione di Named Parameter Idiom. Dai un'occhiata a questo thread su SO. Questo potrebbe non essere esattamente quello di cui hai bisogno, ma offre una certa flessibilità quando vuoi inoltrare le chiamate al ctor predefinito.

+0

puoi spiegare come è collegato l'idioma dei parametri con nome? –

+0

Il secondo link. – dirkgently

4

Non ha a che fare con i costruttori, ma perché pensi di dover creare tutti questi oggetti secondari dinamicamente con nuovi? Questa non è una buona idea: dovresti evitare la creazione dinamica laddove possibile. Non fare tutti quei puntatori ai membri: rendili oggetti reali.

+1

Questo funziona un po 'di tempo. Cosa succede se la classe diventa molto grande e per lo più inutilizzata? Cosa succede se è necessario copiare gli oggetti di classe abbastanza frequentemente? Che cosa succede se diversi membri finiscono per condividere i sottooggetti? –

2

È possibile condividere il codice comune in una funzione membro init() privata.

Esempio:

class Object 
{ 
public: 
    Object(const string &Name); 
    Object(const string &Name, const string &path); 
    ... 
private: 
    void init(); 
}; 

Object::Object(const string &Name) 
{ 
    init(); 
    ... 
} 

Object::Object(const string &Name, const string &path) 
{ 
    init(); 
    ... 
} 

void Object::init() 
{ 
//intialization stuff 
    ... 
} 
+1

Questo ovviamente non funziona se i membri sono const. – Eclipse

+3

Ciò significa che non stai inizializzando, sei in fase di post-inizializzazione. Questo potrebbe essere accettabile, ma di solito è più lento e, quando si hanno membri senza un ctor predefinito, impossibile. –

+0

D'accordo, è lento. Ma evita la duplicazione del codice. –

5

sì, è possibile.
Per semplicità farò finta che il codice originale è:

class Foo { 
public: 
    Foo() : a(0), b(1), x() { } 
    Foo(int x) : a(0), b(1), x(x) { } 

    int get_a() const { return a; } 
    int get_b() const { return b; } 
    int get_x() const { return x; } 
private: 
    int a, b, x; 
}; 

Il codice di refactoring, quindi, è:

class Foo { 
public: 
    Foo() : x() { } 
    Foo(int x) : x(x) { } 

    int get_a() const { return common.a; } 
    int get_b() const { return common.b; } 
    int get_x() const { return x; } 
private: 
    struct Common { 
     Common() : a(0), b(1) { } 
     int a, b; 
    } common; 
    int x; 
}; 
+0

Tranne che hai appena reso impossibile fare riferimento a bar.a, ora è bar.sub.a . –

+0

Ovviamente, ma sarebbe incapsulato in un accessorio come Foo :: getA() quindi non è un problema :) –

+0

Ho modificato per renderlo un po 'più chiaro. –

1

Prima di tutto, avrete perdite di memoria se si don' t implementare la cancellazione degli oggetti allocati nel distruttore. Quindi dovresti definire il tuo distruttore ed eliminare gli oggetti lì.

Se è davvero necessario allocare dinamicamente i membri (non è consigliabile se questa classe possiede l'oggetto membro dati), è possibile avere un metodo privato che esegue l'inizializzazione e è possibile chiamare tale metodo dai costruttori.

+0

tutti i valori del nuovo MemoryColum() stanno entrando nei puntatori intelligenti. Questo li cancellerà automaticamente quando l'oggetto viene distrutto. In teoria. –

0

Hai davvero bisogno di 5 diversi costruttori?

Molto spesso, se è necessario convertire i costruttori, non si desidera un costruttore predefinito.

Tutti gli altri costruttori prendono solo una combinazione diversa della stessa cosa. Potrebbe essere una buona idea avere un solo costruttore che prende tutti i parametri, con un'opzione per ogni parametro che indica il desiderio di invocare un qualche tipo di default per quel parametro.

Ad esempio:

class Object 
{ 
    Object (const string *Name, // can be NULL 
    const string *path, // can be NULL 
    const bool loadMetadata); 

}; 
+0

Prendere i puntatori NULL-a-stringa sarebbe molto inaspettato, ma come ultima possibilità è fattibile, sì. –

1
Object (const string &Name = "", const string &path = "", const bool loadMetadata = false); 

Questo non risolverà tutti i problemi (in particolare, non c'è modo di rappresentare il costruttore con Nome e loadMetaData), ma sarà almeno crollare alcuni dei costruttori in uno.

1

Prefaggerò questo dicendo che io (ovviamente) non conosco i dettagli del tuo sistema o i vincoli che hanno portato alle tue decisioni di progettazione.

Detto questo, una buona regola empirica è che quando una classe inizia a diventare poco soddisfacente - nel momento in cui si iniziano a fare domande su come gestirlo :) - potrebbe essere il momento di ridimensionare quella classe in un paio di sottoclassi più piccole.

Ricorda che una classe dovrebbe fare una cosa molto bene. Se inizi ad avere classi grandi che cercano di fare troppe cose, ti stai allontanando dal buon design OO.

0

Basta inserire l'elenco di inizializzazione all'interno di una MACRO e utilizzarlo.

1

Vorrei utilizzare diversi metodi di fabbrica (metodi statici) che restituirebbero un ptr intelligente alla classe. I nomi dei metodi di fabbrica aiutano anche a documentare PERCHÉ avete bisogno di tutti i diversi parametri.

+0

+1, penso che sia una buona idea, ma non vedo come aggiri i problemi menzionati da Iraimbilanja, ovvero come inizializzare membri e riferimenti di const. Hai ancora bisogno di una varietà di costruttori se vuoi offrire dei valori predefiniti su questi. –

9

Ora un paio di anni dopo abbiamo C++ 11. Se si può utilizzare nel progetto ci sono due opzioni:

Quando i valori di inizializzazione comuni sono conosciute solo in fase di esecuzione è possibile utilizzare la delega costruttori, che significa un costruttore chiama un altro.

// function that gives us the init value at runtime. 
int getInitValue(); 

class Foo 
{ 
    const int constant; 
    int userSet; 

public: 
    // initialize long member list with runtime values 
    Foo() 
     : constant(getInitValue()) 
     , userSet(getInitValue()) 
    {} 

    // other constructors with arguments 
    Foo(int userSetArg) 
     : Foo() 
    { 
     userSet = userSetArg; 
    } 
}; 

o è possibile inizializzare i membri direttamente nella definizione della classe se i loro valori sono noti al momento della compilazione.

class Foo 
{ 
    const int constant = 0; 
    int userSet = 0; 

public: 
    Foo(int userSetArg) : userSet(userSetArg){} 
} 
Problemi correlati