2009-04-17 7 views
12

Tengo un objeto complejo (para mí) con aproximadamente 20 miembros de datos, muchos de los cuales apuntan a otras clases. Entonces, para el constructor, tengo una larga y compleja lista de inicialización. La clase también tiene una docena de diferentes constructores, lo que refleja las diversas formas en que se puede crear la clase. La mayoría de estos elementos inicializados no se modifican entre cada uno de estos diferentes constructores.¿Manejando una clase con una larga lista de inicialización y múltiples constructores?

Mi preocupación aquí es que ahora tengo una gran tirada de código copiado (o mayormente copiado) que, si necesito agregar un nuevo miembro a la clase, puede no entrar en cada una de las listas de inicialización del constructor.

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)) 
{} 

Luego repita lo anterior para los otros constructores. ¿Hay alguna forma inteligente de usar el constructor predeterminado para esto, y luego modificar los resultados para los otros constructores?

Respuesta

13

¿Qué le parece refactorizar los campos comunes en una clase base. El constructor predeterminado para la clase base manejaría la inicialización para la plétora de campos predeterminados. Sería algo como esto:

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)) 
{} 

Sus constructores objeto debe mirar un poco más manejable, ahora:

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) {} 

similares en naturaleza a la respuesta de Iraimbilanja, pero evita la adición de una clase interna para acceder a datos, que podría afectar mucho al código existente. Sin embargo, si ya tiene una jerarquía de clases, puede ser difícil factorizarla en una clase base.

+3

podría querer usar herencia privada porque este es un detalle de implementación. –

+0

o poner la clase base en un espacio de nombres vacío? – jiggunjer

4

Boost::Parameter hace que sea fácil de implementar Named Parameter Idiom. Mira esto thread en SO. Esto puede no ser exactamente lo que necesita, pero proporciona cierta flexibilidad cuando desea reenviar llamadas al ctor predeterminado.

+0

¿Puedes explicar cómo se relaciona el parámetro del idioma idiom? –

+0

El segundo enlace. – dirkgently

4

No tiene nada que ver con los constructores, pero ¿por qué crees que tienes que crear todos esos subobjetos dinámicamente con los nuevos? Esta no es una buena idea: debe evitar la creación dinámica siempre que sea posible. No hagas punteros a todos los miembros: hazlos objetos reales.

+1

Eso funciona algunas veces. ¿Qué pasa si la clase se vuelve muy grande y la mayoría no se usa? ¿Qué sucede si es necesario copiar objetos de clase con bastante frecuencia? ¿Qué pasa si diferentes miembros terminan compartiendo subobjetos? –

2

Puede compartir su código común en una función de miembro privada de init().

Ejemplo:

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

Esto por supuesto no funciona si los miembros son const. – Eclipse

+3

Esto significa que no se está inicializando, ya se está inicializando. Esto puede ser aceptable, pero generalmente es más lento y, cuando tienes miembros sin un administrador predeterminado, es imposible. –

+0

De acuerdo, es lento. Pero evita la duplicidad de código. –

5

sí, es posible.
Por simplicidad voy a fingir que el código original es:

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; 
}; 

El código refactorizado, entonces, es:

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

Excepto que ha hecho que sea imposible hacer referencia a bar.a, ahora es bar.a . –

+0

Por supuesto, pero estaría encapsulado en un accesorio como Foo :: getA() así que no es un problema :) –

+0

Lo edité para que sea un poco más claro. –

1

En primer lugar, tendrá pérdidas de memoria si no' t implementar la eliminación de los objetos asignados en el destructor. Entonces debes definir tu destructor y eliminar los objetos allí.

Si realmente necesita asignar dinámicamente los miembros (no recomiendo si esta clase posee el objeto miembro de datos), puede tener un método privado haciendo toda la inicialización y puede llamar a ese método desde sus constructores.

+0

todos los valores del nuevo MemoryColum() van a los punteros inteligentes. Esto los borrará automáticamente cuando se destruya Object. En teoria. –

0

¿Realmente necesita 5 constructores diferentes?

Muy a menudo, si necesita convertir constructores, tampoco desea un constructor predeterminado.

Sus otros constructores toman simplemente una combinación diferente de la misma cosa. Puede ser una mejor idea tener solo un constructor que tome todos los parámetros, con una opción para cada parámetro que indique el deseo de invocar algún tipo de valor predeterminado para ese parámetro.

Por ejemplo:

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

}; 
+0

Tomar apuntadores NULL a la cadena sería muy inesperado, pero como último recurso es viable, sí. –

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

Esto no va a resolver todos sus problemas (en particular, no hay manera de representar el constructor con nombre y loadMetaData), pero será al menos colapsar algunos de los constructores en uno.

1

Voy a prefacio al decir que yo (obviamente) no conozco los detalles de su sistema, o las limitaciones que llevaron a sus decisiones de diseño.

Dicho esto, una buena regla general es que cuando una clase comienza a desteñirse, más o menos cuando empiezas a hacer preguntas sobre cómo manejarlo :), puede ser hora de refaccionar esa clase en un par de subclases más pequeñas.

Recuerda que se supone que una clase tiene que hacer una cosa muy bien. Si comienzas a tener clases grandes que intentan hacer demasiadas cosas, te alejas del buen diseño OO.

0

Simplemente ponga la lista de inicializadores dentro de un MACRO y termine con ella.

1

Usaría diferentes métodos de fábrica (métodos estáticos) que devolverían una ptr inteligente a su clase. Los nombres de los métodos de fábrica también ayudarían a documentar POR QUÉ usted necesita todos los parámetros diferentes.

+0

+1, creo que es una buena idea, pero no veo cómo se solucionan los problemas mencionados por Iraimbilanja, es decir, cómo inicializar los miembros y referencias de const. Todavía necesita una variedad de constructores si desea ofrecer valores predeterminados en estos. –

9

Ahora, un par de años más tarde tenemos C++ 11. Si se puede usar en su proyecto tiene dos opciones:

Cuando los valores de inicialización común solamente se conocen en tiempo de ejecución puede utilizar delega constructores, lo que significa un constructor llama a otro.

// 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 puede inicializar los miembros directamente en la definición de clase si se conocen sus valores en tiempo de compilación.

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

public: 
    Foo(int userSetArg) : userSet(userSetArg){} 
} 
Cuestiones relacionadas