#c #oop #templates
Вопрос:
Недавно у меня возникла мысль о реализации виртуальных функций без виртуальных таблиц или сохранении указателя с помощью CRTP (хотя static_cast<CRTPamp;>(*this)
вместо этого используется.
Начальная настройка довольно громоздка по сравнению с обычными виртуальными функциями.
Итак, код таков:
namespace detail
{
template<typename T, typename = void>
struct virtual_set_up
{
void operator()(T amp;) {}
};
template<typename T>
struct virtual_set_up<T, std::void_t<decltype(std::declval<T>().set_up())>>
{
void operator()(T amp;t) { t.set_up(); }
};
}
template<typename CRTP>
class base
{
public:
base() {}
void set_up() { detail::virtual_set_up<CRTP>()(static_cast<CRTP amp;>(*this)); }
protected:
~base() = defau<
};
class settable : public base<settable>
{
public:
void set_up() { std::cout << "settable: set_up overridden" << std::endl; }
};
class dummy : public base<dummy>
{
public:
};
int main(int, char **)
{
settable s;
dummy d;
base<settable>amp; baseS = s;
base<dummy>amp; baseD = d;
baseS.set_up();
baseD.set_up();
return 0;
}
Однако есть проблема: virtual_set_up<dummy>
решает специализацию T
с объявленным T::set_up
вызовом SEGFAULT при выполнении. Это происходит потому, что dummy публично наследует от базы, у которой действительно есть set_up
метод.
Учитывая, что предыдущая проблема разрешима, добавляет ли это какую-либо эффективность по сравнению с обычной виртуальной функцией?
Комментарии:
1. возможно, вас заинтересует работа родителей Шона над полиморфизмом. Это всего лишь первый разговор, который я смог найти youtube.com/watch?v=QGcVXgEVMJg , есть еще
Ответ №1:
Чтобы решить вашу бесконечную рекурсию, вы все равно можете сравнить это « amp;dummy::setup
!= amp;base<dummy>::setup
«:
namespace detail
{
template <typename B, typename T, typename = void>
struct virtual_set_up
{
void operator()(Tamp;) {}
};
template <typename B, typename T>
struct virtual_set_up<B, T,
std::enable_if_t<!std::is_same_v<decltype(amp;B::set_up),
decltype(amp;T::set_up)>>>
{
void operator()(Tamp; t) { t.set_up(); }
};
}
template <typename CRTP>
class base
{
public:
base() {}
void set_up() { detail::virtual_set_up<base, CRTP>()(static_cast<CRTP amp;>(*this)); }
protected:
~base() = defau<
};
Но проще было бы переименовать/разделить его в base<CRTP>
template <typename CRTP>
class base
{
public:
base() {}
void set_up() { static_cast<CRTP amp;>(*this).set_up_v(); }
void set_up_v() { std::cout << "basen"; }
protected:
~base() = defau<
};
class settable : public base<settable>
{
public:
void set_up_v() { std::cout << "settable: set_up overridden" << std::endl; }
};
Добавляет ли это эффективности по сравнению с обычной виртуальной функцией?
Весь код решается при компиляции, нет динамической отправки, поэтому нет затрат на виртуальную отправку…
Но у вас нет ничего полиморфного ни здесь: base<dummy>
и base<settable>
это несвязанные классы (вам не std::vector<base>
нужно хранить их вместе). Так что сравнение несправедливо.
В случае, когда все типы известны во время компиляции, компиляторы могут использовать оптимизацию девиртуализации и также удалить накладные расходы на виртуальный вызов.
Комментарии:
1. Да, идея заключалась в полиморфизме во время выполнения, так что в этом нет особого смысла