CRTP Dispatch в C ++ 11

Скажем, у меня есть следующий код:

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 () то, как компилятор будет действовать в любом (или любом другом соответствующем) случае?

2

Решение

Будет ли строка 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() общественности.

5

Другие решения

Общая идиома для 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 блокируется динамическая отправка …

3

Дополнительное словосочетание в строках 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 к
производный класс перед циклом, и использовать [] на производном
учебный класс.

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector