Скажем, у меня есть следующий код:
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->foo_impl(); //A
(*static_cast<Derived*>(this)).foo_impl(); //B
}
};
class Derived : public Base<Derived> {
private:
void foo_impl() {
bar();
}
};
Несколько вопросов:
Будет ли строка A генерировать вызов виртуальной функции? Хотя большинство из того, что я могу найти в Интернете, рекомендует делать это таким образом, для меня я не вижу, как компилятор может выполнять статическую диспетчеризацию, учитывая, что указатель на Derived все еще может фактически указывать на объект типа Derived2, где Derived2: Производный
Исправляет ли строка B проблему, о которой я говорил в моем предыдущем пункте (если применимо)? Похоже, что это так, учитывая, что теперь вызов больше не указатель и, следовательно, с использованием *. позволит избежать вызова виртуальной функции. Но если компилятор обрабатывает разыменованное приведение как ссылочный тип, он все равно может генерировать вызов виртуальной функции … в таком случае, какой обходной путь?
Меняет ли добавление последнего ключевого слова C ++ 11 в foo_impl () то, как компилятор будет действовать в любом (или любом другом соответствующем) случае?
Будет ли строка A генерировать вызов виртуальной функции?
да. foo_impl()
является виртуальным и Derived
переопределяет это. Даже если foo_impl()
в Derived
не является эксплицитно помечен как virtual
находится в базовом классе, и этого достаточно, чтобы сделать его виртуальной функцией.
Исправляет ли строка B проблему, о которой я говорил в моем предыдущем пункте (если применимо)?
нет. Не имеет значения, является ли вызов указателем или ссылкой: компилятор по-прежнему не будет знать, вызываете ли вы функцию foo_impl()
на примере класса, который происходит от Derived
или по прямому Derived
, Таким образом, вызов осуществляется через vtable.
Чтобы понять, что я имею в виду:
#include <iostream>
using namespace std;
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->foo_impl();
(*static_cast<Derived*>(this)).foo_impl();
}
};
class Derived : public Base<Derived> {
public:
void foo_impl() {
cout << "Derived::foo_impl()" << endl;
}
};
class MoreDerived : public Derived {
public:
void foo_impl() {
cout << "MoreDerived::foo_impl()" << endl;
}
};
int main()
{
MoreDerived d;
d.foo(); // Will output "MoreDerived::foo_impl()" twice
}
В заключение:
Добавляет ли последнее ключевое слово C ++ 11 в
foo_impl()
изменить, как компилятор будет действовать в любом (или любом другом соответствующем) случае?
В теории да. final
Ключевое слово сделает невозможным переопределить эту функцию в подклассах Derived
, Таким образом, при выполнении вызова функции foo_impl()
через указатель на Derived
Компилятор мог разрешить звонок статически. Однако, насколько мне известно, компиляторы не требуется сделать это по стандарту C ++.
ЗАКЛЮЧЕНИЕ:
В любом случае, я верю, что вы действительно хотите сделать не объявлять foo_impl()
функция вообще в базовом классе. Обычно это тот случай, когда вы используете CRTP. Кроме того, вам придется объявить класс Base<Derived>
friend
из Derived
если вы хотите получить доступ Derived
«s private
функция foo_impl()
, В противном случае вы можете сделать foo_impl()
общественности.
Общая идиома для CRTP не включает объявление чисто виртуальных функций в базе. Как вы упомянули в одном из комментариев, это означает, что компилятор не будет применять определение члена в производном типе (кроме как через использование, если есть какое-либо использование foo
в базе, что требует наличия foo_impl
в производном типе).
Хотя я бы придерживался общей идиомы и не определял чисто виртуальную функцию в базе, но, если вы действительно чувствуете, что вам нужно это сделать, вы можете отключить динамическую диспетчеризацию, добавив дополнительную квалификацию:
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->Derived::foo_impl();
// ^^^^^^^^^
}
};
Использование дополнительной квалификации Derived::
отключает динамическую диспетчеризацию, и этот вызов будет статически разрешен в Derived::foo_impl
, Обратите внимание, что это будет соответствовать всем обычным предостережениям: у вас есть класс с виртуальной функцией, который оплачивает стоимость виртуального указателя на объект, но вы не можете переопределить эту виртуальную функцию в наиболее производном типе, так как использование в базе CRTP блокируется динамическая отправка …
Дополнительное словосочетание в строках A и B абсолютно не влияет на
сгенерированный код. Я не знаю, кто рекомендует это (я никогда не видел
это), но на практике единственный раз, когда это может иметь эффект
если функция не виртуальная. Просто пиши foo_impl()
, и быть
сделано с этим.
Там является средство избежать вызова виртуальной функции, если
Компилятор знает производный тип. Я видел это используется для
векторные классы (где есть разные реализации,
например нормальный, разреженный и т. д. вектора):
template <typename T>
class Base
{
private:
virtual T& getValue( int index ) = 0;
public:
T& operator[]( int index ) { return getValue( index ); }
};
template <typename T>
class Derived : public Base<T>
{
private:
virtual T& getValue( int index )
{
return operator[]( index );
}
public:
T& operator[]( index )
{
// find element and return it.
}
};
Идея в том, что вы обычно работаете только через ссылки
в базовый класс, но если производительность становится проблемой, потому что
вы используете []
в тесной петле, вы можете dynamic_cast
к
производный класс перед циклом, и использовать []
на производном
учебный класс.