четверг, 20 августа 2009 г.

Нетривиальные конструкторы

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

Представьте себе следующий код (на самом деле, все выглядело намного навороченнее, но суть дела именно такова):
class Dummy
{
bool important_;
int param_;
public:
Dummy(): important_(0), param_(0)
{
}
Dummy(int param): param_(param)
{
Dummy();
}
void DoStuff()
{
if(important_)
{
// A lot of code
}
}
};


Как бы это сказать... код, который был написан делает не то, что думает автор, причем сразу в двух смыслах :-)

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

Второй намного менее понятен, по крайней мере тому, кто плохо знаком с языком - в этом случае конструктор по умолчанию вообще не вызывается!

Я так и вижу, как автор (кстати, очень неглупый человек, правда, у нас давно уже не работает), пишет что-то типа:
Dummy(int param): Dummy(), param_(param)
{
}


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

На самом деле правда проста - конструктор в такой ситуации вообще не вызывается!
Точнее, он вызывается, но только в другом контексте - на стеке создаетсмя локальный объект класса X без параметров, который в конце полного выражения (";") благополучно разрушается. Это несколько не то, что ожидается...

В C++ вообще иногда довольно сложно понять, что происходит - например:
Dummy d; // объект класса Dummy
Dummy d(); // объявление функции d, которая возвращает экземпляр класса Dummy
Dummy d(a); // если a - класс, объявление, если, например, переменная -
// cоздание локального объекта Dummy, с параметризованного a


Все, что может являться объявлением, таковым и является, все возможные толкования разрешаются именно в пользу объявления (раздел 6.8 стандарта).

Upd: в C++0x, видимо, появятся делегирующие конструкторы. Коротко, для тех, кто не хочет продираться сквозь draft стандарта, об этом можно почитать здесь.

Комментариев нет: