Аргументы против метода "initialize ()" вместо конструкторов - PullRequest
7 голосов
/ 19 марта 2012

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

class Foo
{
  public:
    Foo() { /* Do nothing here */ }
    bool initialize() { /* Do all the initialization stuff and return true on success. */ }
    ~Foo() { /* Do all the cleanup */ }
};

Теперь я могу ошибаться, но для меня этот метод initialize() ужасен. Я считаю, что это отменяет всю цель иметь конструкторов.

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

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

Спасибо.

Ответы [ 6 ]

8 голосов
/ 19 марта 2012

Инициализация как одношаговая (конструктор), так и двухэтапная (с методом init) - это полезные шаблоны. Лично я считаю, что исключение любого из них является ошибкой, хотя, если ваши соглашения полностью запрещают использование исключений, вы запрещаете одношаговую инициализацию для конструкторов, которые могут завершиться с ошибкой.

В общем, я предпочитаю одношаговую инициализацию, потому что это означает, что ваши объекты могут иметь более сильные инварианты. Я использую двухэтапную инициализацию только тогда, когда считаю, что объект может существовать в «неинициализированном» состоянии. Это имеет смысл или полезно.

При двухэтапной инициализации допустимо, чтобы ваш объект находился в неинициализированном состоянии, поэтому каждый метод, работающий с объектом, должен знать и правильно обрабатывать Дело в том, что это может быть в неинициализированном состоянии. Это аналогично работе с указателями, где неправильно полагать, что указатель не равен NULL. И наоборот, если вы выполняете всю свою инициализацию в конструкторе и терпите неудачу с исключениями, вы можете добавить «объект всегда инициализируется» в ваш список инвариантов, и таким образом это становится проще и безопаснее. делать предположения о состоянии объекта.

4 голосов
/ 19 марта 2012

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

Не могу не подчеркнуть больше:
Создание исключения из конструктора в случае сбоя - лучший и единственный лаконичный способ обработки сбоев конструкции объекта.

1 голос
/ 19 марта 2012

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

С другой стороны, если успех или иное построение зависит от ввода пользователя, то сбой не является исключительным условием, а скорее является частью нормального, ожидаемого времени выполненияповедение, которое вы должны проверить.В этом случае у вас должен быть конструктор по умолчанию, который создает объект в «недопустимом» состоянии, и функция инициализации, которая может быть вызвана либо в конструкторе, либо позже, что может или не может быть успешным.Возьмите std::ifstream в качестве примера.

Так что скелет вашего класса может выглядеть так:

class Foo
{
    bool valid;
    bool initialize(Args... args) { /* ... */ }

public:
    Foo() : valid(false) { }
    Foo(Args... args) : valid (false) { valid = initialize(args...); }

    bool reset(Args... args)   // atomic, doesn't change *this on failure
    {
        Foo other(args...);
        if (other) { using std::swap; swap(*this, other); return true; }
        return false;
    }

    explicit operator bool() const { return valid; }
};
0 голосов
/ 26 июня 2019

Я понимаю, что это очень старая тема, но я хотел добавить что-то, что не было явно указано (или, возможно, просто подразумевается). В C ++, когда конструктор выдает исключение, объект не считается «созданным», и поэтому его деструктор не будет вызываться как часть разматывания исключения.

Это может быть очень реальным мотивирующим фактором для использования метода initialise () вместо того, чтобы делать это в конструкторе. Сложный объект, занимающий много места в памяти и т. П., Должен был бы отмотать всю эту работу вручную, если конструктор выдал исключение.

Если используется метод initialise (), объект уже «создан» во время инициализации, и, следовательно, будет вызван деструктор объекта.

Итак, да, выполнение инициализации в конструкторе "приятнее", но это также возлагает на программиста большую нагрузку на правильную очистку, если что-то пойдет не так. Частичный подход к очистке приведет к очень уродливому коду.

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

0 голосов
/ 19 марта 2012

Это боль, но у вас нет другого выбора, если вы хотите избежать исключения из конструктора. Есть и другой вариант, не менее болезненный: выполнить всю инициализацию в конструкторе, а затем вам нужно проверить, был ли объект успешно создан (например, оператор преобразования в bool или метод IsOK). Жизнь тяжела, ..... а потом ты умрешь: (

0 голосов
/ 19 марта 2012

Это зависит от случая.

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

Если Foo содержит объекты, они будут инициализированы дважды, один раз в конструкторе, один раз в методе initialize, так чтонедостаток.

ИМО, самый большой недостаток в том, что вы должны помнить, чтобы позвонить initialize.Какой смысл создавать объект, если он недопустим?

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

Если, однако, они хотят какой-то ленивой инициализации, это допустимо.

...