В чем причина устранения возможности прекращения распространения методов виртуальности?
Позвольте мне быть более ясным: в C ++, независимо от того, пишете ли вы «virtual void foo ()» или «void foo ()» в производном классе, он будет виртуальным до тех пор, пока в базовом классе foo объявлен виртуальным.
Это означает, что вызов foo () через производный указатель * приведет к поиску в виртуальной таблице (в случае, если функция производная 2 переопределяет foo), даже если это поведение не требуется программисту.
Позвольте мне привести пример (который кажется мне довольно вопиющим) того, как было бы полезно остановить распространение виртуальности:
template <class T>
class Iterator // Here is an iterator interface useful for defining iterators
{ // when implementation details need to be hidden
public:
virtual T& next() { ... }
...
};
template <class T>
class Vector
{
public:
class VectIterator : public Iterator<T>
{
public:
T& next() { ... }
...
};
...
};
В приведенном выше примере базовый класс Iterator может использоваться для достижения формы «стирания типа» гораздо более понятным и объектно-ориентированным способом. (Увидеть http://www.artima.com/cppsource/type_erasure.html для примера стирания типа.)
Но, тем не менее, в моем примере можно использовать объект Vector :: VectIterator напрямую (что будет сделано в большинстве случаев) для доступа к реальному объекту без использования интерфейса.
Если бы виртуальность не распространялась, вызовы Vector :: VectIterator :: next () даже из указателя или ссылки не были бы виртуальными и могли бы быть встроенными и работать эффективно, как если бы интерфейс Iterator не существовал.
C ++ 11 добавил контекстное ключевое слово final
для этого.
class VectIterator : public Iterator<T>
{
public:
T& next() final { ... }
...
};
struct Nope : VecIterator {
T& next() { ... } // ill-formed
};
Простой ответ: не смешивайте конкретные и абстрактные интерфейсы! Очевидным подходом в вашем примере будет использование неvirtual
функция next()
который делегирует virtual
функция, например, do_next()
, Производный класс переопределит do_next()
возможно делегирование неvirtual
функция next()
, Так как next()
функции скорее всего inline
Там нет никаких затрат, связанных с делегированием.
На мой взгляд, одной из причин такого распространения является virtual
деструкторов. В C ++, когда у вас есть базовый класс с некоторыми virtual
Методы, которые вы должны определить деструктор virtual
, Это связано с тем, что некоторый код может иметь указатель базового класса, который фактически указывает на производный класс, а затем пытается удалить этот указатель (см. Это вопрос для более подробной информации).
Определив деструктор в базовом классе как vritual
Вы можете убедиться, что все указатели базового класса, указывающие на производный класс (на любом уровне наследования), будут удалены должным образом.
Я думаю, причина в том, что было бы очень сложно удалить виртуальность частично через структуру наследования (у меня есть пример сложности ниже).
Однако, если вас беспокоит микрооптимизация удаления нескольких виртуальных вызовов, я бы не стал беспокоиться. Пока вы встраиваете код виртуального дочернего метода И ваш итератор передается по значению, а не по ссылке, хороший оптимизирующий компилятор будет уже быть в состоянии увидеть динамический тип во время компиляции и встроить все это для вас, несмотря на то, что это виртуальный метод!
Но для полноты рассмотрим следующее на языке, который можно де-виртуализировать:
class A
{
public:
virtual void Foo() { }
};
class B : public A
{
public:
void Foo() { } // De-virtualize
};
class C: public B
{
public:
void Foo() { } // not virtual
};
void F1(B* obj)
{
obj->Foo();
static_cast<A*>(obj)->Foo();
}
C test_obj;
F1(test_obj); // Which two methods are called here?
Вы можете установить правила, какие именно методы будут вызываться, но очевидный выбор будет варьироваться от человека к человеку. Гораздо проще просто распространять виртуальность метода.