Почему должны быть определены неиспользуемые виртуальные функции?

Я нахожу довольно странным, что неиспользуемые виртуальные функции все еще должны быть определены, в отличие от неиспользуемых обычных функций. Я немного понимаю неявные vtables и vpointers, которые создаются при создании объекта класса - это в некоторой степени отвечает на вопрос (что функция должна быть определена так, чтобы указатели на можно определить виртуальную функцию), но это отодвигает мой запрос еще дальше.

Зачем нужно создавать запись vtable для функции, если нет абсолютно никакой вероятности, что виртуальная функция вообще будет вызвана?

class A{
    virtual bool test() const;
};

int main(){
    A a; //error: undefined reference to 'vtable for A'
}

Несмотря на то, что я объявил A::test(), он никогда не использовался в программе, но все равно выдает ошибку. Может ли компилятор не запустить программу и понять, что test() никогда не вызывался, и, следовательно, не требовать для него записи vtable? Или это неразумно ожидать от компилятора?


person AntiElephant    schedule 09.10.2015    source источник
comment
Вы не можете создать экземпляр виртуального класса. Но тривиально создать нулевую функцию или функцию, которая просто возвращает константу объявленного типа.   -  person Logicrat    schedule 09.10.2015


Ответы (3)


Потому что разработчику компилятора неизбежно будет очень трудно решить проблему, когда полезность возможности оставлять виртуальные функции неопределенными в лучшем случае сомнительна. У авторов компиляторов наверняка есть задачи получше.

Кроме того, вы используете эту функцию, даже если вы ее не вызываете. Вы берете его адрес.

person Edward Strange    schedule 09.10.2015
comment
Я бы даже сказал, что это невозможно решить: скажем, у вас есть библиотека, которую вы открываете с помощью dlopen, которая дает вам указатель на класс с неопределенной виртуальной функцией. Компилятор не может это поймать, так что это будет ошибка времени выполнения... - person Florian; 10.10.2015

ОП говорит, что он уже знает о vtables и vpointers, поэтому он понимает, что есть разница между неиспользуемыми виртуальными функциями и неиспользуемыми невиртуальными функциями: на неиспользуемую невиртуальную функцию нигде не ссылаются, а на виртуальную функцию ссылаются как минимум один раз в vtable своего класса. Итак, по сути, вопрос заключается в том, почему компилятору не хватает ума воздержаться от размещения ссылки на виртуальную функцию в vtable, если эта функция нигде не используется. Это позволило бы функции также стать неопределенной.

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

Некоторые инструменты поддерживают такой анализ, они называют его «глобальным» анализом или чем-то подобным. Вы даже можете найти его встроенным в некоторые компиляторы и доступным через некоторые опции компилятора. Но он никогда не включается по умолчанию, потому что это чрезвычайно замедлит компиляцию.

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

Итак, просто определите пустую виртуальную функцию, содержащую ASSERT(FALSE), и продолжайте свою жизнь.

person Mike Nakis    schedule 09.10.2015
comment
@AlexD хорошо, ОП говорит, что он уже знает о vtables и vpointers, поэтому я не чувствовал, что мне нужно решать эту проблему. Но вы правы, лучше заявить, чем оставить подразумеваемым. Итак, я изменил свой ответ. - person Mike Nakis; 10.10.2015
comment
Компилятор обычно видит только один файл .cpp за раз, поэтому он не знает, есть ли у вас где-то исходный файл, который вызывает эту функцию. Если бы у меня был исходный файл, вызывающий эту функцию, разве в этом исходном файле не был бы включен заголовок для этого класса, в котором отсутствует виртуальное определение? Как мне вызвать функцию-член, не определив класс в исходном файле? - person AntiElephant; 10.10.2015
comment
Извините, под вызовом функции вы имели в виду определить ее? - person AntiElephant; 10.10.2015
comment
@AntiElephant нет, под вызовом я подразумеваю то, что обычно означает вызов, то есть вызов. - person Mike Nakis; 10.10.2015
comment
@AntiElephant, если бы компилятор мог видеть, что вы никогда не вызываете функцию из какого-либо файла .cpp, то теоретически он мог бы пропустить создание своей записи vtable, и в этом случае не было бы ошибкой оставить эту функцию неопределенной. - person Mike Nakis; 10.10.2015

Весь смысл виртуальных функций в том, что их можно вызывать через указатель базового класса. Если вы никогда не использовали виртуальную функцию базового класса, то зачем вы ее определили? Если он используется, вам нужно либо оставить родительскую реализацию (если она не чисто виртуальная), либо определить свою собственную реализацию, чтобы код, использующий ваши объекты через базовый класс, действительно мог его использовать. В этом случае функция используется, просто не используется напрямую.

person Vincent Fourmond    schedule 10.10.2015