Обычно вы получаете такой код для объектов в стеке:
MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
// error-handling code
} else {
// phew, we got away with it. Now for the next object...
}
И это для объектов в куче. Я предполагаю, что вы переопределяете глобальный оператор new чем-то, что возвращает NULL вместо throw, чтобы сэкономить, помня использовать везде nothrow new:
MyClassWithNoThrowConstructor *foo = new MyClassWithNoThrowConstructor();
if (foo == NULL) {
// out of memory handling code
} else if (foo->init(bar, baz, etc) != 0) {
delete foo;
// error-handling code
} else {
// success, we can use foo
}
Очевидно, что если вы можете, используйте умные указатели, чтобы избавить вас от необходимости запоминать удаления, но если ваш компилятор не поддерживает исключения должным образом, у вас могут возникнуть проблемы с получением Boost или TR1. Я не знаю.
Вы также можете захотеть структурировать логику по-разному или абстрагировать объединенные new и init, чтобы избежать глубоко вложенного «кода стрелки» при работе с несколькими объектами, и объединить обработку ошибок между двумя случаи неудачи. Вышесказанное является лишь основной логикой в ее наиболее кропотливой форме.
В обоих случаях конструктор устанавливает все значения по умолчанию (он может принимать некоторые аргументы, при условии, что то, что он делает с этими аргументами, может не получиться, например, если он просто хранит их). Затем метод init может выполнять реальную работу, которая может привести к сбою, и в этом случае возвращает 0 success или любое другое значение для сбоя.
Вероятно, вам нужно обеспечить, чтобы каждый метод init во всей вашей кодовой базе сообщал об ошибках одинаково: вы не хотите, чтобы некоторые возвращали 0 успешно или отрицательный код ошибки, некоторые возвращали 0 успешно или положительно код ошибки, некоторые возвращают bool, некоторые возвращают объект по значению, у которого есть поля, объясняющие ошибку, некоторые устанавливают глобальный номер ошибки и т. д.
Возможно, вы сможете быстро взглянуть на некоторые документы по API класса Symbian в Интернете. Symbian использует C ++ без исключений: у него есть механизм, называемый «Leave», который частично компенсирует это, но он не подходит для выхода из конструктора, поэтому у вас есть одна и та же базовая проблема с точки зрения разработки безотказных конструкторов и отсрочки сбоя при сбое. операции для инициации процедур. Конечно, в Symbian подпрограмме init разрешено выходить, так что вызывающей программе не нужен код обработки ошибок, который я указал выше, но с точки зрения разделения работы между конструктором C ++ и дополнительным вызовом init это тоже самое.
Общие принципы включают в себя:
- Если ваш конструктор хочет получить значение откуда-то таким способом, который может привести к сбою, перенесите его в init и оставьте значение по умолчанию инициализированным в ctor.
- Если ваш объект содержит указатель, задайте ему значение null в ctor и установите его "правильно" в init.
- Если ваш объект содержит ссылку, либо измените его на (умный) указатель, чтобы он мог начинаться с нуля, либо заставьте вызывающую функцию передать значение в конструктор в качестве параметра вместо генерации его в ctor.
- Если в вашем конструкторе есть члены типа объекта, то все в порядке. Их ctors тоже не будут выбрасывать, так что вполне нормально построить своих членов (и базовые классы) в списке инициализаторов обычным способом.
- Убедитесь, что вы отслеживаете, что установлено, а что нет, чтобы деструктор работал при сбое инициализации.
- Все функции, кроме конструкторов, деструктора и init, могут предполагать, что init завершился успешно, при условии, что вы документируете для своего класса, что недопустимо вызывать любой метод, кроме init, до тех пор, пока init не будет вызван и завершится успешно.
- Вы можете предложить несколько функций инициализации, которые в отличие от конструкторов могут вызывать друг друга, так же, как для некоторых классов вы предлагаете несколько конструкторов.
- Вы не можете предоставить неявные преобразования, которые могут потерпеть неудачу, поэтому, если ваш код в настоящее время полагается на неявные преобразования, которые генерируют исключения, вам придется перепроектировать. То же самое касается большинства перегрузок операторов, поскольку их возвращаемые типы ограничены.