Нетривиальные конструкторы Aug 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</code></pre>

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

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