Обработка класса с длинным списком инициализации и несколькими конструкторами? - PullRequest
12 голосов
/ 17 апреля 2009

У меня есть (для меня) сложный объект с примерно 20 членами данных, многие из которых являются указателями на другие классы. Так что для конструктора у меня большой длинный сложный список инициализации. Класс также имеет дюжину различных конструкторов, отражающих различные способы создания класса. Большинство этих инициализированных элементов неизменны между каждым из этих различных конструкторов.

Меня беспокоит то, что теперь у меня есть большой кусок скопированного (или преимущественно скопированного) кода, который, если мне нужно добавить новый член в класс, может не попасть в каждый из списков инициализации конструктора.

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

Затем повторите, как указано выше, для других конструкторов. Есть ли какой-нибудь разумный способ использовать конструктор по умолчанию для этого, а затем изменить результаты для других конструкторов?

Ответы [ 12 ]

14 голосов
/ 18 апреля 2009

Как насчет рефакторинга общих полей в базовый класс. Конструктор по умолчанию для базового класса будет обрабатывать инициализацию множества полей по умолчанию. Выглядело бы примерно так:

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

Ваши конструкторы объектов теперь должны выглядеть немного более управляемыми:

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

По своему характеру похож на ответ Iraimbilanja, но избегает добавления внутреннего класса для доступа к данным, что может повлиять на большую часть существующего кода. Однако, если у вас уже есть иерархия классов, может быть трудно разделить ее на базовый класс.

10 голосов
/ 10 июля 2014

Теперь через пару лет у нас есть C ++ 11. Если вы можете использовать его в своем проекте, у вас есть два варианта:

Когда общие значения инициализации известны только во время выполнения, вы можете использовать делегирующие конструкторы, что означает, что один конструктор вызывает другой.

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

или вы можете инициализировать элементы непосредственно в определении класса, если их значения известны во время компиляции.

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

public:
    Foo( int userSetArg) : userSet(userSetArg){}
}
5 голосов
/ 17 апреля 2009

Да, это возможно.
Для простоты я сделаю вид, что оригинальный код:

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

Реорганизованный код:

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;
};
4 голосов
/ 17 апреля 2009

Не для конструкторов, но как вы думаете, почему вы должны динамически создавать все эти подобъекты с новыми? Это не очень хорошая идея - вы должны избегать динамического создания, где это возможно. Не заставляйте всех этих членов указывать - делайте их настоящими объектами.

4 голосов
/ 17 апреля 2009

Boost :: Parameter упрощает реализацию Именованный элемент именованных параметров . Проверьте это поток на SO. Это может быть не совсем то, что вам нужно, но обеспечивает некоторую гибкость, когда вы хотите перенаправлять вызовы на ctor по умолчанию.

2 голосов
/ 17 апреля 2009

Вы можете поделиться их общим кодом в закрытой функции-члене init ().

Пример:

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 голос
/ 18 апреля 2009

Я бы использовал разные фабричные методы (статические методы), которые возвращали бы умный ptr вашему классу. Имена фабричных методов также помогут документировать, ПОЧЕМУ вам нужны все различные параметры.

1 голос
/ 18 апреля 2009

Я предвосхищу это, сказав, что я (очевидно) не знаю деталей вашей системы или ограничений, которые привели к вашим проектным решениям.

При этом хорошее эмпирическое правило заключается в том, что, когда класс начинает становиться непривлекательным - примерно в то время, когда вы начинаете задавать вопросы о том, как с ним обращаться :) - может быть, пора реорганизовать этот класс в пару меньших подклассы.

Помните, что класс должен делать одну вещь очень хорошо . Если вы начинаете иметь большие классы, которые пытаются делать слишком много вещей, вы отдаляетесь от хорошего ОО-дизайна.

1 голос
/ 18 апреля 2009
Object (const string &Name = "", const string &path = "", const bool loadMetadata = false);

Это не решит всех ваших проблем (в частности, нет способа представить конструктор с помощью Name и loadMetaData), но это по крайней мере свернет некоторые конструкторы в одну.

1 голос
/ 17 апреля 2009

Прежде всего, у вас будут утечки памяти, если вы не реализуете удаление выделенных объектов в деструкторе. Поэтому вы должны определить свой деструктор и удалить там объекты.

Если вам действительно нужно динамически распределить члены (я не рекомендую, если этот класс владеет объектом-членом данных), вы можете иметь закрытый метод, выполняющий всю инициализацию, и вы можете вызывать этот метод из своих конструкторов. *

...