用模板实现虚函数的覆盖机制
我最近想到了在没有虚拟表的情况下实现虚拟函数或使用 CRTP 存储指针(尽管static_cast<CRTP&>(*this)改为使用。
与传统的虚拟功能相比,初始设置相当麻烦。
所以代码是:
namespace detail
{
template<typename T, typename = void>
struct virtual_set_up
{
void operator()(T &) {}
};
template<typename T>
struct virtual_set_up<T, std::void_t<decltype(std::declval<T>().set_up())>>
{
void operator()(T &t) { t.set_up(); }
};
}
template<typename CRTP>
class base
{
public:
base() {}
void set_up() { detail::virtual_set_up<CRTP>()(static_cast<CRTP &>(*this)); }
protected:
~base() = default;
};
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>& baseS = s;
base<dummy>& baseD = d;
baseS.set_up();
baseD.set_up();
return 0;
}
但是有一个问题:virtual_set_up<dummy>解决了Twith 声明T::set_up的特殊化导致执行时的 SEGFAULT。发生这种情况是因为 dummy 是从 base 公开继承的,它确实有一个set_up方法。
鉴于前面的问题是可以解决的,与传统的虚函数相比,这是否会增加任何效率?
回答
为了解决你的无限递归,你可能仍然比较“ &dummy::setup!= &base<dummy>::setup”:
namespace detail
{
template <typename B, typename T, typename = void>
struct virtual_set_up
{
void operator()(T&) {}
};
template <typename B, typename T>
struct virtual_set_up<B, T,
std::enable_if_t<!std::is_same_v<decltype(&B::set_up),
decltype(&T::set_up)>>>
{
void operator()(T& t) { t.set_up(); }
};
}
template <typename CRTP>
class base
{
public:
base() {}
void set_up() { detail::virtual_set_up<base, CRTP>()(static_cast<CRTP &>(*this)); }
protected:
~base() = default;
};
演示
但更简单的是重命名/拆分一个 base<CRTP>
template <typename CRTP>
class base
{
public:
base() {}
void set_up() { static_cast<CRTP &>(*this).set_up_v(); }
void set_up_v() { std::cout << "basen"; }
protected:
~base() = default;
};
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>它们存储在一起)。所以比较是不公平的。
对于在编译时已知所有类型的情况,编译器可能会使用去虚拟化优化并消除虚拟调用的开销。