Является ли вызов чисто виртуальных функций косвенно из конструктора всегда неопределенным поведением? - PullRequest
3 голосов
/ 02 февраля 2011

Я работаю над сборкой Cppcheck в AIX с помощью компилятора xlC (см. предыдущий вопрос ). Все классы проверки происходят от класса Check, конструктор которого регистрирует каждый объект в глобальном списке:

check.h

class Check {
public:
    Check() {
        instances().push_back(this);
        instances().sort();
    }
    static std::list<Check *> &instances();
    virtual std::string name() const = 0;
private:
    bool operator<(const Check *other) const {
        return (name() < other->name());
    }
};

checkbufferoverrun.h

class CheckBufferOverrun: public Check {
public:
    // ...
    std::string name() const {
        return "Bounds checking";
    }
};

Проблема, которая у меня возникает, связана с вызовом instances().sort(). sort() вызовет Check::operator<(), что вызовет Check::name() для каждого указателя в статическом списке instances(), но только что добавленный в список экземпляр Check еще не полностью выполнил свой конструктор (потому что он все еще внутри Check::Check()). Следовательно, должно быть неопределенное поведение для вызова ->name() для такого указателя до завершения работы конструктора CheckBufferOverrun.

Это действительно неопределенное поведение, или мне здесь не хватает тонкости?

Обратите внимание, что я не думаю, что вызов sort() является строго обязательным, но в результате Cppcheck запускает все свои программы проверки в детерминированном порядке. Это влияет только на выходные данные в том порядке, в котором обнаружены ошибки, что приводит к сбою некоторых тестовых случаев, поскольку они ожидают выходные данные в определенном порядке.

Обновление : Вопрос, как указано выше, до сих пор (в основном) остается. Тем не менее, я думаю, что реальная причина, по которой вызов sort() в конструкторе не вызывал проблем (т.е. сбой при вызове чисто виртуальной функции), заключается в том, что Check::operator<(const Check *) на самом деле никогда не вызывается sort()! Вместо этого sort(), кажется, сравнивает указатели вместо этого. Это происходит как в g++, так и в xlC, что указывает на проблему с самим кодом Cppcheck.

Ответы [ 4 ]

4 голосов
/ 02 февраля 2011

Да, это не определено. Стандарт конкретно так говорит в 10.4 / 6

Функции-члены могут вызываться из конструктора (или деструктора) абстрактного класса; эффект от виртуального вызова (10.3) чисто виртуальной функции прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), не определен.

2 голосов
/ 02 февраля 2011

Это правда, что вызов чисто виртуальной функции из конструктора всегда неопределенное поведение.

Виртуальный указатель нельзя считать установленным до тех пор, пока конструктор не запустится полностью (закрывая "}"), и, следовательно, любой вызов виртуальной функции (или чисто виртуальной функции) должен быть установлен во время самой компиляции. (статически связанный вызов).

Теперь, если виртуальная функция является чисто виртуальной функцией, компилятор обычно вставляет свою собственную реализацию для такой чисто виртуальной функции, поведение по умолчанию которой заключается в генерировании ошибки сегментации. Стандарт не предписывает, какой должна быть реализация чисто виртуальной функции, но большинство компиляторов C ++ используют вышеуказанный стиль.

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

instances().push_back(this);
instances().sort();

тогда, может быть, это поможет увидеть, что происходит.

0 голосов
/ 02 февраля 2011

Я думаю, что ваша настоящая проблема в том, что вы связали две вещи: базовый класс Checker и некоторый механизм регистрации (производных) экземпляров Check.

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

Может быть, вы могли бы сделать что-то вроде этого: Checker получит защищенный ctor (в любом случае, он абстрактный, и поэтому только проверенные классы должны вызывать ctor Checker).

Производные классы также имеют защищенные ctors и открытый статический метод («именованный шаблон конструктора») для создания экземпляров. Этот метод создает новости для подкласса Checker, и они передают его (полностью созданный на этом этапе) в класс CheckerRegister (который также является абстрактным, поэтому пользователи могут реализовать свои собственные, если это необходимо).

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

Один простой способ сделать это - использовать статический метод getCheckerRegister в Checker.

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

class CheckBufferOverrun: public Check { защищенный: CheckBufferOverrun: Check («Проверка границ») { // поскольку у каждого производного есть имя, почему бы просто не передать его как аргумент? } общественности: CheckBufferOverrun makeCheckBufferOverrun () { CheckBufferOverrun that = new CheckBufferOverrun ();

   // get the singleton, pass it something fully constructed
   Checker.getCheckerRegister.register(that) ;
   return that;
}

Если это выглядит так, что это будет много шаблонного кода, напишите шаблон. Если вы беспокоитесь об этом, поскольку каждый экземпляр шаблона в C ++ является реальным и уникальным классом, напишите базовый класс без шаблонов, который будет регистрировать любой производный от Checker.

0 голосов
/ 02 февраля 2011

Пока строительство объекта не закончено, чистая виртуальная функция не может быть вызвана.Однако, если он объявлен чисто виртуальным в базовом классе A, а затем определен в B (производном от A), конструктор C (производный от B) может вызвать его, поскольку построение B завершено.в этом случае используйте вместо этого статический конструктор:

class check {
private Check () { ... }
public:
    static Check* createInstance() {
        Check* check = new Check();
        instances().push_back(check);
        instances().sort();
    }
...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...