Это способ построения и разрушения классов.
Сначала строится база, потом получается. Таким образом, в конструкторе Base Derived еще не был создан. Поэтому ни одна из его функций-членов не может быть вызвана. Поэтому, если конструктор Base вызывает виртуальную функцию, это не может быть реализация из Derived, это должна быть реализация из Base. Но функция в Base является чисто виртуальной и вызывать нечего.
При разрушении сначала Деривед уничтожается, затем База. Итак, еще раз в деструкторе Base нет объекта Derived для вызова функции, только Base.
Между прочим, это только неопределенно, когда функция все еще является чисто виртуальной. Так что это четко определено:
struct Base
{
virtual ~Base() { /* calling foo here would be undefined */}
virtual void foo() = 0;
};
struct Derived : public Base
{
~Derived() { foo(); }
virtual void foo() { }
};
Обсуждение перешло к предложению альтернатив, которые:
- Это может привести к ошибке компилятора, как это делает попытка создать экземпляр абстрактного класса.
Код примера, несомненно, будет выглядеть примерно так:
базовый класс
{
// другие вещи
виртуальная пустота init () = 0;
виртуальная очистка void () = 0;
};
Base::Base()
{
init(); // pure virtual function
}
Base::~Base()
{
cleanup(); // which is a pure virtual function. You can't do that! shouts the compiler.
}
Здесь ясно, что то, что вы делаете, навлечет на вас неприятности. Хороший компилятор может выдать предупреждение.
- это может привести к ошибке ссылки
Альтернатива состоит в том, чтобы найти определение Base::init()
и Base::cleanup()
и вызвать его, если оно существует, в противном случае вызвать ошибку связи, то есть обработать очистку как не виртуальную для целей конструкторов и деструкторов.
Проблема в том, что она не будет работать, если у вас есть не виртуальная функция, вызывающая виртуальную функцию.
class Base
{
void init();
void cleanup();
// other stuff. Assume access given as appropriate in examples
virtual ~Base();
virtual void doinit() = 0;
virtual void docleanup() = 0;
};
Base::Base()
{
init(); // non-virtual function
}
Base::~Base()
{
cleanup();
}
void Base::init()
{
doinit();
}
void Base::cleanup()
{
docleanup();
}
Эта ситуация, как мне кажется, выходит за рамки возможностей как компилятора, так и компоновщика. Помните, что эти определения могут быть в любом модуле компиляции. Здесь нет ничего противозаконного в том, что конструктор и деструктор вызывают init () или cleanup (), если вы не знаете, что они собираются делать, и нет ничего противозаконного в том, что init () и cleanup () вызывают чистые виртуальные функции, если вы не знаете из где они вызываются.
Компилятору или компоновщику совершенно невозможно это сделать.
Поэтому стандарт должен разрешать компиляцию и ссылку и помечать это как «неопределенное поведение».
Конечно, если реализация существует, компилятор может использовать ее, если это возможно. Неопределенное поведение не означает, что должно произойти сбой. Просто в стандарте не говорится, что он должен его использовать.
Заметьте, что в этом случае деструктор вызывает функцию-член, которая вызывает чисто виртуальную, но как вы знаете, что она сделает это? Это может быть вызов чего-то в совершенно другой библиотеке, которая вызывает чисто виртуальную функцию (предположим, что доступ есть).
Base::~Base()
{
someCollection.removeMe( this );
}
void CollectionType::removeMe( Base* base )
{
base->cleanup(); // ouch
}
Если CollectionType существует в совершенно другой библиотеке, то здесь не может быть никакой ошибки ссылки. Дело в том, что комбинация этих вызовов плохая (но ни один из них не является ошибочным). Если removeMe собирается вызывать чисто-виртуальную очистку (), ее нельзя вызвать из деструктора Base, и наоборот.
И последнее, что вы должны помнить о Base::init()
и Base::cleanup()
, это то, что даже если они имеют реализации, они никогда не вызываются через механизм виртуальной функции (v-таблица). Они будут вызываться только явно (с использованием полной квалификации имени класса), что означает, что в действительности они не являются виртуальными. То, что вам разрешено давать им реализации, возможно, вводит в заблуждение, возможно, это не очень хорошая идея, и если вам нужна такая функция, которую можно вызывать через производные классы, возможно, она лучше защищена и не является виртуальной.
По сути: если вы хотите, чтобы функция имела поведение не чистой виртуальной функции, например, чтобы вы дали ей реализацию, и она была вызвана на этапе конструктора и деструктора, то не определяйте ее как чисто виртуальную. Зачем определять это как то, чего не хочешь?
Если все, что вы хотите сделать, это предотвратить создание экземпляров, вы можете сделать это другими способами, такими как: - Сделать деструктор чисто виртуальным.- Сделать все конструкторы защищенными