В чем причина неявного распространения виртуальности?

Я работаю с C++ всего 2-3 месяца и недавно узнал об идентификаторе final, который идет после виртуальной функции. До сих пор я считал, что отсутствие virtual остановит распространение виртуальности, но я ошибался. Он неявно распространяется.

Мой вопрос заключается в следующем. Зачем разрешать неявное распространение? Почему наличие virtual не может сделать функцию виртуальной, а отсутствие virtual сделать функцию не виртуальной? Лучше в некоторых обстоятельствах? или это было в те дни, когда впервые был представлен виртуальный?


Согласно Clifford answer, есть даже компилятор, выдающий предупреждение при отсутствии virtual.


почему виртуальность методов неявно распространяется в c

Я ожидал, что ссылка выше ответит на мой вопрос, но это не так.

------------ Добавление -------------

Есть комментарии о полезности этой функции. Я думаю, что ключевое слово final в виртуальной функции — это то, что делает функцию девиртуализированной. Функция больше не может быть переопределена, поэтому производный класс должен повторно объявить функцию независимо от того, имеет ли она то же имя или нет. Если final отличается от девиртуализации, помогите мне понять это. Если final не отличается, то полезность девиртуализации очевидна из факта введения final. Я согласен с тем, что форсирование явного виртуального приведет к ошибкам, но мне любопытно, есть ли другие причины.


person Sgene9    schedule 21.08.2016    source источник
comment
как только он в какой-то момент объявлен виртуальным, все методы, соответствующие сигнатуре, сохраняются в виртуальной таблице, независимо от ключевого слова virtual или нет. Разве final не для Java? Или это C++11 и я что-то пропустил?   -  person Jean-François Fabre    schedule 21.08.2016
comment
Совершенно не очевидно, насколько полезно остановить распространение. У вас есть убедительный пример? (Обратите внимание, что final гораздо сильнее, чем остановить виртуальное распространение здесь.)   -  person molbdnilo    schedule 21.08.2016
comment
@Jean-FrançoisFabre final — это C++11, поэтому вы отстаете на несколько лет. (Это полезно.)   -  person molbdnilo    schedule 21.08.2016
comment
Основной ответ на ваш вопрос: «потому что Бьерн Страуструп».   -  person user207421    schedule 21.08.2016
comment
Как вариант, можно обвинить иммигрантов.   -  person Karoly Horvath    schedule 21.08.2016
comment
И даже Бьярне не придумал это сам, а позаимствовал из языка Simula, который уже в 1960-х годах были классы, наследование и это правило для виртуальных функций. Таким образом, одним из ответов может быть Так было всегда.   -  person Bo Persson    schedule 21.08.2016
comment
@molbdnilo В принципе, везде, где я хотел бы предотвратить его переопределение. Я не понимаю, чем final как идентификатор функции, а не как идентификатор класса, отличается от остановки виртуального распространения здесь, что предотвращает переопределение.   -  person Sgene9    schedule 22.08.2016
comment
@Sgene9 Sgene9 Где бы я ни хотел, это не пример полезности, а тем более убедительный. Опубликуйте конкретную ситуацию и объясните, как это решит реальную проблему.   -  person molbdnilo    schedule 22.08.2016
comment
Было бы довольно запутанно, если бы vehicle->go() вызывал Car::go(), но не Toyota::go()   -  person M.M    schedule 23.08.2016
comment
Разрешить подклассам делать одну и ту же функцию невиртуальной означало бы нарушить принцип подстановки лисков. Делая это, вы заставляете подклассы вводить специальные хаки, чтобы проверять, какой тип что-то имеет, прежде чем что-то с ним делать, и вносите ненужную сложность и возможность ошибки. Представьте, что вы передали свой объект функции по ссылке на базовый класс, и в результате была вызвана функция, отличная от той, которую вы ожидали. Это был бы хаос!   -  person Paul Rooney    schedule 23.08.2016
comment
@molbdnilo Я дал больше разъяснений по вопросу, отредактировав его.   -  person Sgene9    schedule 23.08.2016
comment
@PaulRooney не будет ли также нарушать принцип объявления некоторой виртуальной функции как final? Скажем, A имеет виртуальную функцию foo, а B, который наследует A, объявляет foo как final. C, который наследует B, объявляет другую функцию с именем foo. А и С взаимозаменяемы?   -  person Sgene9    schedule 23.08.2016
comment
Он не сможет объявить foo с той же подписью. В этом суть. Он мог бы объявить это с другой подписью, но тогда это не было бы переопределением.   -  person Paul Rooney    schedule 23.08.2016
comment
@PaulRooney Если бы вы дали ответ с содержанием, которое вы мне предоставили, я бы проверил ответ. В основном я понимаю, что final не де-виртуализируется. final запрещает производным классам объявлять функцию с той же сигнатурой, что приводит к запрету переопределения. Поскольку вы показали мне, что final отличается от девиртуализации, теперь на мой вопрос дан ответ.   -  person Sgene9    schedule 24.08.2016


Ответы (1)


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

Что вообще означает «девиртуализация» функции? Если во время выполнения вы вызываете «девиртуализированную» функцию для объекта, будет ли вместо этого использоваться статический тип указателя? Что произойдет, если у статического типа есть виртуальная функция, а у типа времени выполнения ее нет?

#include <iostream>

struct A     {  virtual void f() const { std::cout << "A"; }  };
struct B : A {          void f() const { std::cout << "B"; }  };
struct C : B {  virtual void f() const { std::cout << "C"; }  };
struct D : C {          void f() const { std::cout << "D"; }  };

void f(const A& o) { o.f(); }

int main()
{
                // "devirtualized"     real C++

    f(A{});     // "A"                 "A"
    f(B{});     // "A" or "B"?         "B"
    f(C{});     // "C"?                "C"
    f(D{});     // oh god              "D"
}

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

person isanae    schedule 22.08.2016
comment
C++ обычно старается держаться подальше от функций, которые требуют дисциплины, чтобы работать правильно... ну, до тех пор, пока вы не начнете больше изучать язык или не придете к нему из мира C. Тогда это немного ломается :P - person Sebastian Lenartowicz; 23.08.2016
comment
@SebastianLenartowicz RAII, умные указатели, более жесткая система типов, исключения, вариативные шаблоны, список можно продолжить. О чем вообще ты говоришь? - person isanae; 23.08.2016
comment
Я имею в виду, достаточно верно. Я просто придерживаюсь мнения, что C++ (который, не поймите меня неправильно, я люблю) имеет много маленьких ошибок, если вы не готовы к ним. - person Sebastian Lenartowicz; 23.08.2016
comment
@isanae f( A ) = A f( B ) = A f( C ) = C f( D ) = C struct A объявила виртуальную функцию, struct B унаследовала A, struct C унаследовала B, но так как B::f() не является виртуальной, C объявил другую виртуальную функцию, структура D унаследовала C - person Sgene9; 23.08.2016
comment
struct A { virtual void f() const { std::cout ‹‹ A; } }; @isanae struct B : A { virtual void f() final const { std::cout ‹‹ B; } }; struct C : B { virtual void f() const { std::cout ‹‹ C; } }; struct D : C { virtual void f() final const { std::cout ‹‹ D; } }; В принципе, я думаю, что ваш код с моей идеей должен вести себя так, как указано выше. - person Sgene9; 23.08.2016
comment
@Sgene9 Sgene9 Для меня это не имеет смысла. - person isanae; 24.08.2016
comment
@isanae Мне жаль, что я задал неясный вопрос и попросил дать четкий ответ. Возможно, вы захотите ознакомиться с комментариями Пола Руни в разделе «Вопрос», которые помогли мне решить мой вопрос. - person Sgene9; 24.08.2016
comment
@Sgene9 Sgene9 Ваш вопрос был: Зачем разрешать неявное распространение? как концепция, а не более техническая. Прекращает ли final распространение? Мой ответ на последнее был бы совсем другим. - person isanae; 24.08.2016
comment
@isanae Вы правы. Вопрос был основан на моем убеждении, что final останавливает распространение. Извините за ошибку. - person Sgene9; 26.08.2016
comment
Я думаю, что этот ответ на самом деле не проверяет правильные (неправильные) предположения, которые могут быть у людей, потому что void f(const A& o) всегда вызывает через тип A, следовательно, всегда вызывает виртуальный метод. Более интересно, что выведет void f(const B& o). До сегодняшнего дня я думал, что это просто выведет "B" независимо от того, с чем это будет вызываться. - person Felix Dombek; 25.04.2018