CRTP避免动态多态

发布于 2024-07-08 20:59:05 字数 37 浏览 7 评论 0原文

如何在 C++ 中使用 CRTP 以避免虚拟成员函数的开销?

How can I use CRTP in C++ to avoid the overhead of virtual member functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

铃予 2024-07-15 20:59:05

有两种方法。

第一个是为类型结构静态指定接口:

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

因此,将结构/接口定义和函数中的编译时类型推导结合起来,您可以进行静态分派而不是动态分派。 这就是静态多态性的本质。

There are two ways.

The first one is by specifying the interface statically for the structure of types:

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.
};

The second one is by avoiding the use of the reference-to-base or pointer-to-base idiom and do the wiring at compile-time. Using the above definition, you can have template functions that look like these:

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

So combining the structure/interface definition and the compile-time type deduction in your functions allows you to do static dispatch instead of dynamic dispatch. This is the essence of static polymorphism.

半城柳色半声笛 2024-07-15 20:59:05

我自己一直在寻找有关 CRTP 的不错的讨论。 Todd Veldhuizen 的科学 C++ 技术是一本很棒的书此资源 (1.3) 以及许多其他高级技术(例如表达式模板)。

另外,我发现您可以在 Google 图书上阅读 Coplien 的大部分原始 C++ Gems 文章。 也许情况仍然如此。

I've been looking for decent discussions of CRTP myself. Todd Veldhuizen's Techniques for Scientific C++ is a great resource for this (1.3) and many other advanced techniques like expression templates.

Also, I found that you could read most of Coplien's original C++ Gems article at Google books. Maybe that's still the case.

凉栀 2024-07-15 20:59:05

我必须查找 CRTP。 然而,完成此操作后,我发现了一些关于静态多态性的内容。 我怀疑这就是你问题的答案。

事实证明,ATL 相当广泛地使用了这种模式。

I had to look up CRTP. Having done that, however, I found some stuff about Static Polymorphism. I suspect that this is the answer to your question.

It turns out that ATL uses this pattern quite extensively.

爱她像谁 2024-07-15 20:59:05

采用严格签名检查的 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 有一个名为
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。 所以,
即使我们只为接受 bool 的签名设置调度,has_foo 也会解析为 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;

这很好,但仅此并不能强制执行签名检查。 严格执行
签名检查,我们必须正确定义模板模板参数
操作。 为此,我们将使用成员的 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>

以这种方式定义我们的 Op 允许我们只分派到带有
签名完全匹配。

// 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
}

编写一个为非重载成员函数创建调度程序的宏
会很简单,但是制作一个支持重载函数的会
更具挑战性一点。 如果有人愿意贡献这一点,我欢迎
添加。

CRTP/SFINAE Static Dispatching with Strict Signature Checking

This solution for static dispatching uses CRTP and SFINAE, which is not new.
What is unique about this solution is that it also enforces strict signature
checking, which allows us to statically dispatch overloaded methods in the same
way dynamic dispatching works for virtual functions.

To begin, let's first look at the limitations of a traditional solution using
SFINAE. The following was taken from Ben Deane's CppCon 2016 Lightning Talk
“A Static Alternative to Virtual Functions, Using Expression 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()))

Using the above code, the template instantiation has_complete<DerivedClass>
will, in general, do what you would expect. If DerivedClass has a method named
Complete that accepts a std::string, the resulting type will be
std::true_type.

What happens when you want to overload a function?

template <class Derived>
struct Base {
    std::string foo(bool);
    std::string foo(int);
    ...
};

struct Derived : public Base<Derived>
{
    std::string foo(int);
};

In this case, Derived does, in fact, have a method named foo that accepts a
bool because bool is implicitly convertible to int. Therefore,
even if we only set up dispatching for the signature that accepts a bool, has_foo<Derived> would resolve to std::true_type, and the call would be
dispatched to Derived::foo(int). Is this what we want? Probably not, because
this is not the way that virtual functions work. A function can only override a
virtual function if the two signatures match exactly. I propose that we make a
static dispatch mechanism that behaves in the same way.

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;

That's nice, but that alone doesn't enforce signature checks. To perform strict
signature checking, we have to properly define the template template parameter
Op. To do this, we will make use of a std::integral_constant of a member
function pointer. Here's what that looks like:

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>

Defining our Ops in this way allows us to dispatch only to methods with an
exact signature match.

// 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>;

Now let's put it all together.

#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
}

Writing a macro that creates a dispatcher for a non-overloaded member function
would be simple enough, but making one that supports overloaded functions would
be a bit more challenging. If anybody cares to contribute that, I'd welcome the
addition.

眼前雾蒙蒙 2024-07-15 20:59:05

这个维基百科答案拥有您所需的一切。 即:

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 静态多态性的开销为:

  • 内存:每个模板实例化的基数复制
  • 运行时:1函数指针调用 + 无论 static_cast 正在做什么

This Wikipedia answer has all you need. Namely:

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();
};

Although I don't know how much this actually buys you. The overhead of a virtual function call is (compiler dependent, of course):

  • Memory: One function pointer per virtual function
  • Runtime: One function pointer call

While the overhead of CRTP static polymorphism is:

  • Memory: Duplication of Base per template instantiation
  • Runtime: One function pointer call + whatever static_cast is doing
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文