静态多态

定义与实现原理

静态多态又叫做CRTP,全程是Curiously recurring template pattern,是基于C++模板实现的。其方法是:一个继承类 Derived,它继承自一个以 Derived为模板参数的基类 Base。原理基础是:

  • 模板参数的二次查找
#include <functional>
#include <iostream>

template <typename Derived, typename ValueType> class Base {
public:
  using and_then_callback = std::function<void(ValueType &)>;
  /// 函数是公共接口函数,所有继承类通过该接口实现多态
  /// 接口的实现在基类, 与虚函数相反,虚函数多态的实现在继承类
  /// 中间有两次接口的约定:
  /// 1. 因为在基类中调用继承类的方法,所以所有继承类必须实现相关方法
  /// 2. 基类的方法暴露给用户,为提供给用户的接口
  Derived &and_then(const and_then_callback &callback) {
    /// 将指针cast成继承类的指针
    auto derived = static_cast<Derived *>(this);
    if (derived->has_data()) {
      callback(derived->get_data());
      // 可以在基类中调用继承类的成员,这也是这个设计模式的核心,因为按照正常逻辑,
      // 此处这个函数基类并不知道(先声明,后使用),但是因为是模板类,
      // 所以该类的实例化其实是在derived->other_implementation()之后进行的,
      // 所以编译可以通过
      derived->other_implementation();
    }
    return *derived;
  }
};

template <typename ValueType>
class Derived_A : public Base<Derived_A<ValueType>, ValueType>

{
public:
  template <typename... Argu> void set_data(const Argu &...args) {
    data = ValueType(args...);
    flag = true;
  }
  ValueType &get_data() { return data; }
  void other_implementation() {
    std::cout << "Derived_A implementation is called\n";
  }
  bool has_data() { return flag; }

private:
  ValueType data;
  bool flag = false;
};

template <typename ValueType>
class Derived_B : public Base<Derived_B<ValueType>, ValueType> {
public:
  template <typename... Argu> void set_data(const Argu &...args) {
    data = ValueType(args...);
    flag = true;
  }
  ValueType &get_data() { return data; }
  void other_implementation() {
    std::cout << "Derived_B implementation is called\n";
  }
  bool has_data() { return flag; }

private:
  ValueType data;
  bool flag = false;
};

struct MyType {
  int a;
  float b;
  MyType(const int arg1, const float arg2) : a(arg1), b(arg2){};
  MyType() = default;
};

int main(int argc, char const *argv[]) {
  Derived_A<MyType> DA;
  Derived_B<MyType> DB;

  DA.template set_data(1u, 2.1f);
  DB.template set_data(2u, 3.2f);
  // and_then是基类的成员函数,继承类没有,但是在这里实现了多态,不同的成员可以有不同的实现
  // 但是没有vptr table等,没有运行时消耗,是静态多态,可以节约资源
  DA.and_then(
      [](MyType &a) -> void { std::cout << "from DA: a = " << a.a << "\n"; });
  DB.and_then(
      [](MyType &b) -> void { std::cout << "from DB: a = " << b.a << "\n"; });
  return 0;
}

用法一:将基类的接口扩展到继承类

当基类的方法在继承类中不存在时,会将基类的接口方法扩展到继承类。在上面的例子中, and_then 方法在继承类中不存在,继承类扩展了这些接口。

  • 扩展后用户直接使用继承类
  • 继承类必须保证在基类的方法实现中用到的所有方法,也就是说:
    • 继承类提供接口给基类
    • 基类提供接口给用户

用法二:实现静态接口

当继承类中有基类相同的方法时,继承类的方法会覆盖基类的方法,而通过CRTP,在基类中可以直接调用继承类的同名方法。如果用户使用基类对象,就会产生与使用虚函数相同的效果。

  • 基类和继承类实现同名方法
  • 在基类的同名方法中调用继承类的同名方法
  • 用户直接使用基类对象,调用的是实际继承类的方法
#include <functional>
#include <iostream>

template <typename Derived, typename ValueType> class Base {
public:
  /// 基类和继承类有相同的方法
  Derived &print_value() {
    /// 将指针cast成继承类的指针
    auto derived = static_cast<Derived *>(this);
    /// 在基类中调用继承类的同名方法
    derived->print_value();
    return *derived;
  }
};

template <typename ValueType>
class Derived_A : public Base<Derived_A<ValueType>, ValueType> {
public:
  template <typename... Argu> void set_data(const Argu &...args) {
    data = ValueType(args...);
    flag = true;
  }
  ValueType &get_data() { return data; }
  void print_value() { std::cout << "Derived_A printing\n"; }
  bool has_data() { return flag; }

private:
  ValueType data;
  bool flag = false;
};

template <typename ValueType>
class Derived_B : public Base<Derived_B<ValueType>, ValueType> {
public:
  using fn_print_value_type = std::function<ValueType &>;
  template <typename... Argu> void set_data(const Argu &...args) {
    data = ValueType(args...);
    flag = true;
  }
  ValueType &get_data() { return data; }
  void print_value() { std::cout << "Derived_B printing\n"; }
  bool has_data() { return flag; }

private:
  ValueType data;
  bool flag = false;
};

struct MyType {
  int a;
  float b;
  MyType(const int arg1, const float arg2) : a(arg1), b(arg2){};
  MyType() = default;
};

/// 用户
/// 注意这里用户必须是个模板,因为不但Derived_A和Derived_B是两种类型
/// 他们的基类也是两种类型,不能用同一个基类指针指向它们,需要使用
/// 模板函数的方式来同时支持Derived_A和Derived_B作为参数传入
/// 静态多态在使用时也是静态的
template <typename Derived, typename ValueType>
void print_base(Base<Derived, ValueType> &base) {
  base.print_value();
}

int main(int argc, char const *argv[]) {
  Derived_A<MyType> DA;
  Derived_B<MyType> DB;

  DA.template set_data(1u, 2.1f);
  DB.template set_data(2u, 3.2f);
  /// DA 和 DB 都可以传入,且分别调用各自的方法
  print_base(DA);
  print_base(DB);
  return 0;
}

静态多态和虚函数多态的对比

CRTP静态多态和虚函数动态多态区别在于没有 vptrvptr table 等动态运行时的函数查找机制,是在编译时运用模板的特性来实现静态的多态:

  • 静态多态因为没有运行时损耗,运行时效率要比动态多态要高
  • CRTP要求继承类必须实现指定的函数供基类(接口类)调用,所以区别在于:
    • 动态多态要求继承类必须实现相应的虚函数
    • 静态多态要求继承类必须实现相应的成员方法,供基类接口方法调用
  • 静态多态只能在模板类使用,使用范围不如虚函数

静态多态是不能完全替代虚函数的,因为静态多态要求在编译时已经知道所有的类型信息,其多态是通过模板的特化来实现的;虚函数不要求在编译时知道所有类型信息,只需要知道虚接口类型,而在运行时自动实现多态(其实运行时也不知道实际的类型信息,只是通过虚函数表实现多态)。

Mixin

Wikipedia的定义:

In object-oriented programming languages, a mixin (or mix-in)1[3][4] is a class that contains methods for use by other classes without having to be the parent class of those other classes.

根据这个定义C++并没有原生支持Mixin,因为C++必须通过模板参数+继承的方式实现Mixin。

C++中的Mixin就是类继承它的模板参数。本质上就是继承。

#include <functional>
#include <iostream>

/// Mixins
class SayEn {
public:
  void say_hi() { std::cout << "Hello\n"; }
};

class SayCh {
public:
  void say_hi() { std::cout << "你好\n"; }
};

template <typename Mixin> class Person : public Mixin {};

template <typename Mixin1, typename Mixin2>
class BilangPersion : public Mixin1, public Mixin2 {};

int main(int argc, char const *argv[]) {
  /// Person 可以通过继承不同的Mixin获得不同的方法
  Person<SayEn> en_person;
  Person<SayCh> ch_person;

  BilangPersion<SayCh, SayEn> bi_persion;

  en_person.say_hi();
  ch_person.say_hi();

  /// 必须具体指定是哪个类型的方法
  bi_persion.SayEn::say_hi();
}

CRTP基类用作Mixin

既然Mixin可以被作为模板参数被其他类继承其功能,那么一个CRTP的基类可以作为一个Mixin类被其他类继承,这样其他类也变成了一个CRTP的基类,且这个继承类可以重新 override 原CRTP基类的方法:

#include <functional>
#include <iostream>

/// CRTP 基类
template <typename Derived> class Animal {
public:
  /// 接口方法
  void run() { this->get_child()->default_behavior(); }

  void bark() { this->get_child()->default_behavior(); }

  /// 所有基类要求实现,作为接口方法的默认实现
  void default_behavior() { exit(1); }

private:
  /// 类型转为继承类
  Derived *get_child() { return static_cast<Derived *>(this); }
};

/// Mixin

template <typename Mixin> class LivingThing : public Mixin {};

/// Pet现在也是CRTP接口类
template <typename T> class Pet : public LivingThing<Animal<T>> {};

/// 使用
class Dog : public Pet<Dog> {
public:
  void run() { std::cout << "Dog running.." << std::endl; }
  void default_behavior() {
    std::cout << "Dog is lazy, it is doing nothing.." << std::endl;
  }
};

int main() {
  Dog d;
  /// Dog没有实现bark方法,但是继承了CRTP的默认接口方法实现
  /// 注意:在CRTP基类bark方法中用到的Derived方法必须在Drived类中实现
  d.bark();
}