Среди этих правил есть: однозначное создание\инициализация объекта и его удаление\деинициализация. Кроме того, должна быть эффективная система обработки ошибок, позволяющая в любой момент прерывать исполнение и передавать работу контролееру за исключительными ситуациями: обработчику исключений. Теперь самое главное правило - любой объектный язык должен эффективно совмещать эти правила вместе. Правильно, я говорю про исключения в конструкторе.
Любой опытный разработчик знает о том, что в С++ нельзя бросать исключения в конструкторе и это приводит к ужасным последствиям, я постораюсь объяснить почему.
Среда исполнения имеет кучу различных объектов на пмяти, инструкции по их созданию и удалению, и два состояния у каждого объекта: инициализирован\не инициализирован. В случае успешной инициализации он попадает в общую кучу и будет обсолютно корректно уничтожен и в случае необходимости. Однако, объект который не смог завершить свою инициализацию может содержать часть инициализированных полей, деструктор для которых не будет вызван.
В действительности эти состояния появляются сами сосбой и получить управления ими невозможно. Вот какой код генерирует компилятор для каждого класса:
- среда исполнения выделяет область памяти
- передает управление конструктору который пробегается по области памяти и заносит новые значения
- возвращает управление среде выполенния
Конструктор, который заполняет память выделенную для его класса, условно, пробегается сверху вниз и заполняет содержимое новыми значениями. Эти значения могут быть ссылки на только-что выделенную динамическую память, которой класс хочет управлять в дальнейшем. Однако, для среды исполнения эта вновь выделенная память никак не связана с ее владельцем и в случае ошибки останется висеть в куче. Если пробежавшись до половины памяти класса он вызывает исключение и передает управление среде исполнения блок памяти считается целиком не инициализированным и на него не будет вызван дестркутор класса.
На первый взгляд кажется, что среда исполнения должна в любом случае вызывать деструктор на эту область памяти, которая была вполовину инициализирована конструктором. Но нет ответа на вопрос: что делать если деструктор вызовится на область памяти, которая не инициализирована? В этом случае деструктор класса может попытаться осободить\обратиться даже на не ицициализированный указатель памяти и это привидет к куда более серезеным последствиям чем утечка.
Как например в примере, приведенным ниже, если бы среда исполнения несмотря на искючение вызвала бы деструктор класса m (хочу обратить внимание, что она вызывает десткрукторы других, успешно инициализированных, классов) то переменная j содержала бы неопределенное значение и в деструкторе небылобы возможности проверить ее на валидность.
class My
{
int i;
int *k;
int *j;
public:
My ()
{
i = 0;
k = new int[5];
throw exception();
j = new int[5];
}
~My ()
{
fprint("%x\n",i);
fprint("%x\n",k);
fprint("%x\n",j);
delete k;
delete j;
}
};
int main(int c, const char** a)
{
try
{
int k = 0;
{
My m;
} // в этом месте мог бы быть вызван деструктор класса m. Но так как память m была не инициализированна этого не происходит.
// хочу обратить внимание, что если бы до класса My m стоял бы другой успешно инициализированный класс, его дестркуртор был бы вызван
}catch(std::exception &e)
{
// solve it
}
return 0;
};
Другая, более сложная ситуация, когда класс содержит локальные переменные-классы. В этом случае если класс My2 бросает исключение при инициализации своих локальных перменных еще не войдя в тело конструктора.
class My2
{
int i;
MyNormal m;
My m;
My2() try
{
// body constructor
} catch(std::exception &e)
{
// solve it
}
};
В этом случае я часто встречаю решение данной проблеммы - игнорирование исключений. Разработчики вставляют конструкцию try/catch в тело конструктора и любые исключительные ситуации отправляют в системный журнал - это крайне не допустимо! В использовать журналирование - означает игнорировать ошибку и создавать класс не инициализированный, не пригодный для использования, что порождает ошибку второго уровня. Это в свою очередь опять таки не допустимо.
Вывод:
Из всего вышесказанного я делаю вывод, что в С++ нельзя инициализировать класс в его конструкторе, нельзя использовать локальные переменные состоящие не из простых типов и указателей (тоже простой тип), так как это в ряде случаев приводит к серезным ошибкам. Следовательно - С++ не объектный язык :)
Для того чтобы писать на С++ вам необходимо проработать с языком не меньше 5 лет и узнать обо всех тонкостях, выработать и научится хорошему стилю программирования и исключать из своих разработок код привиденный выше.


3 коммент.: