【发布时间】:2010-09-20 16:58:23
【问题描述】:
如何在 C++ 中使用 CRTP 来避免虚成员函数的开销?
【问题讨论】:
标签: c++ templates virtual crtp
如何在 C++ 中使用 CRTP 来避免虚成员函数的开销?
【问题讨论】:
标签: c++ templates virtual crtp
有两种方法。
第一个是为类型的结构静态指定接口:
template <class Derived>
struct base {
void foo() {
static_cast<Derived *>(this)->foo();
};
};
struct my_type : base<my_type> {
void foo(); // required to compile.
};
struct your_type : base<your_type> {
void foo(); // required to compile.
};
第二个是避免使用基址引用或基址指针惯用法,并在编译时进行连接。使用上述定义,您可以拥有如下所示的模板函数:
template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
obj.foo(); // will do static dispatch
}
struct not_derived_from_base { }; // notice, not derived from base
// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload
因此,结合函数中的结构/接口定义和编译时类型推导,您可以进行静态分派而不是动态分派。这就是静态多态的本质。
【讨论】:
not_derived_from_base 不是源自base,也不是源自base...
template<class T> bar(base2<T> &obj) { obj.quux(); }——即具有不同bar()实现的第二个基类——并且CRTP的实用性变得明显。
我自己一直在寻找关于 CRTP 的体面讨论。 Todd Veldhuizen 的 Techniques for Scientific C++ 是用于此 (1.3) 和许多其他高级技术(如表达式模板)的绝佳资源。
另外,我发现您可以在 Google 书籍中阅读 Coplien 的大部分原始 C++ Gems 文章。也许现在仍然如此。
【讨论】:
dynamic_cast 或虚拟方法。
我不得不查找CRTP。然而,在这样做之后,我发现了一些关于Static Polymorphism 的东西。我怀疑这就是你问题的答案。
事实证明ATL 相当广泛地使用了这种模式。
【讨论】:
带有严格签名检查的 CRTP/SFINAE 静态调度
这种静态调度解决方案使用了 CRTP 和 SFINAE,这并不新鲜。 该解决方案的独特之处在于它还强制执行严格的签名 检查,它允许我们在同一个文件中静态地调度重载的方法 虚拟函数的动态调度方式。
首先,让我们先看看传统解决方案的局限性 SFINAE。以下摘自 Ben Deane 的 CppCon 2016 Lightning Talk “虚拟函数的静态替代方案,使用表达式 SFINAE。”
#define SFINAE_DETECT(name, expr) \
template <typename T> \
using name##_t = decltype(expr); \
template <typename T, typename = void> \
struct has_##name : public std::false_type {}; \
template <typename T> \
struct has_##name<T, void_t<name##_t<T>>> : public std::true_type {};
// detect CommonPrefix(string)
SFINAE_DETECT(common_prefix,
declval<T>().CommonPrefix(std::string()))
使用上面的代码,模板实例化has_complete<DerivedClass>
一般来说,会做你所期望的。如果DerivedClass 有一个名为
Complete 接受 std::string,结果类型将是
std::true_type.
当你想重载一个函数时会发生什么?
template <class Derived>
struct Base {
std::string foo(bool);
std::string foo(int);
...
};
struct Derived : public Base<Derived>
{
std::string foo(int);
};
在这种情况下,Derived 实际上有一个名为 foo 的方法,它接受一个
bool 因为bool 可以隐式转换为int。所以,
即使我们只为接受布尔值的签名设置调度,has_foo<Derived> 将解析为std::true_type,调用将是
发送到Derived::foo(int)。这是我们想要的吗?可能不会,因为
这不是虚函数的工作方式。一个函数只能覆盖一个
如果两个签名完全匹配,则为虚函数。我建议我们做一个
以相同方式运行的静态调度机制。
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
这很好,但仅此一项并不能强制执行签名检查。严格执行
签名检查,我们必须正确定义模板模板参数
Op。为此,我们将使用成员的std::integral_constant
函数指针。看起来是这样的:
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>
以这种方式定义我们的Ops 允许我们仅调度具有
完全匹配的签名。
// Resolves to std::integral_constant<std::string(T::*)(bool), &Derived::foo>
using foo_bool_ic = dispatcher_t<foo_op_b, Derived, Defaults>;
// Resolves to std::integral_constant<std::string(T::*)(int), &Defaults::foo>
using foo_int_ic = dispatcher_t<foo_op_i, Derived, Defaults>;
现在让我们把它们放在一起。
#include <iostream>
#include <experimental/type_traits>
#include <string>
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
// Used to deduce class type from a member function pointer
template <class R, class T, class... Args>
auto method_cls(R(T::*)(Args...)) -> T;
struct Defaults {
std::string foo(bool value) { return value ? "true" : "false"; }
std::string foo(int value) { return value ? "true" : "false"; }
// Ensure that the class is polymorphic so we can use dynamic_cast
virtual ~Defaults() {};
};
template <class Derived>
struct Base : Defaults {
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>;
std::string foo(bool value) {
auto method = dispatcher_t<foo_op_b, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
std::string foo(int value) {
auto method = dispatcher_t<foo_op_i, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
};
struct Derived : Base<Derived> {
std::string foo(bool value) { return value ? "TRUE" : "FALSE"; }
};
int main() {
Derived d;
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(true) << std::endl; // TRUE
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(1) << std::endl; // true
}
编写一个为非重载成员函数创建调度程序的宏 会很简单,但是制作一个支持重载函数的会 更具挑战性。如果有人愿意贡献,我会欢迎 补充。
【讨论】:
This Wikipedia 回答有你所需要的一切。即:
template <class Derived> struct Base
{
void interface()
{
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
static void static_func()
{
// ...
Derived::static_sub_func();
// ...
}
};
struct Derived : Base<Derived>
{
void implementation();
static void static_sub_func();
};
虽然我不知道这实际上给你买了多少。虚函数调用的开销是(当然取决于编译器):
而 CRTP 静态多态的开销是:
【讨论】: