C++CRTP概念与应用和concept

发布时间 2023-11-18 21:11:52作者: 橙皮^-^

一、奇异递归模板模式(Curiously Recurring Template Pattern, CRTP)[1]

CRTP出现在C++中一种设计方法,方法操作:派生类Derived将自身作为模板参数传递给基类模板,这样可以在基类的实现中访问特定的类型的this指针
代码形式:在基类公开接口,在派生类实现该接口

template<typename T>
class Base
{
	//派生类T将自身作为参数传递给基类
	//在基类内部可以使用static_cast<T&>(*this)转化为派生类类型
	//实现在Base类中访问Derived的成员
	void Interface(){
		static_cast<T*>(this)->Implementation();
	}
};

class Derived : public Base<Derived>
{
	//...实现被Base类型调用的成员函数
	void Implementation();
};

二、CRTP应用

2.1 多态链(Polymorphic chaining):也称为方法链,每个方法返回一个对象,可以将调用链接起来而不需要变量来存储中间结果。

这里先看下为啥不能用基本继承实现,而是需要CRTP实现。
简单的方法链Printer类[1:1]

#include <iostream>

class Printer
{
public:
  Printer(std::ostream& pstream): m_stream(pstream){}

  template<typename T>
  Printer& print(T&& t) { m_stream << t; return *this;}

  template<typename T>
  Printer& println(T&& t) { m_stream << t << std::endl; return *this;}
private:
  std::ostream& m_stream;
};

int main(int argc, char *argv[])
{
  Printer printer(std::cout);
  printer.println("hello").print(500);
	//输出:
	//hello
	//500
}

如果增加派生类CoutPrinter

class CoutPrinter : public Printer
{
public:
    CoutPrinter() : Printer(std::cout) {}

    CoutPrinter& SetConsoleColor(Color c)
    {
        switch (c)
        {
        case Color::red:
          std::cout << "\033[0;31m " << std::endl;
          break;
		 //...
        default:
          break;
        }
        return *this;
    }
};

int main(int argc, char *argv[])
{
  CoutPrinter().print("hello").SetConsoleColor(Color::red);
	//编译报错error: ‘class Printer’ has no member named ‘SetConsoleColor’
	//原因在于print是Printer的成员函数,返回的是Printer实例而不是CoutPrinter实例
}

使用CRTP避免这个问题,实现多态链

#include <iostream>
#include "color.h"

//Base class
template <typename Derived>
class Printer
{
public:
  Printer(std::ostream& pstream): m_stream(pstream){}

  template<typename T>
  Derived& print(T&& t)
  {
    m_stream << t;
    return static_cast<Derived&>(*this);
  }
  template<typename T>
  Derived& println(T&& t)
  {
    m_stream << t;
    return static_cast<Derived&>(*this);
  }
private:
  std::ostream& m_stream;
};

//Derived class
class CoutPrinter : public Printer<CoutPrinter>
{
public:
    CoutPrinter() : Printer(std::cout) {}

    CoutPrinter& SetConsoleColor(Color c)
    {
        switch (c)
        {
        case Color::red:
          std::cout << "\033[0;31m " << std::endl;
          break;
        default:
          break;
        }
        return *this;
    }
};

int main(int argc, char* argv[])
{
  CoutPrinter().print("Hello ").SetConsoleColor(Color::red).println("Printer!");
	//Hello
	//Printer!(显示红色字体)
}

2.2 实现对象计数器

template<typename T>
class Counter
{
  static inline int objects_created = 0;
  static inline int objects_alive = 0;

  Counter()
  {
    ++objects_created;
    ++objects_alive;
  }

  Counter(const concept&)
  {
    ++objects_created;
    ++objects_alive;
  }
protected:
  ~Counter()
  {
    --objects_alive;
  }
};

class X : Counter<X>
{
    // ...
};

class Y : Counter<Y>
{
    // ...
};

如果X和Y继承同一个基类Z而不是模板类Counter,Z记录的实例总数就是X和Y的实例总和。使用CRTP可以对不同类对象进行统计,是因为Counter 和 Counter是俩个不同的类。

2.3 实现多态复制构建clone方法

2.3.1 使用虚函数进行实现
#include <memory>
// 定义一个基类和一个clone的纯虚函数
class AbstractShape {
public:
  virtual ~AbstractShape () = default;
  virtual std::unique_ptr<AbstractShape> clone() const = 0;
protected:
   AbstractShape() = default;
   AbstractShape(const AbstractShape&) = default;
   AbstractShape(AbstractShape&&) = default;
};

//派生类实现基类的clone虚函数实现多态
class Square : public AbstractShape
{
public:
  virtual std::unique_ptr<Square> clone() {
    return std::make_unique<Square>(static_cast<Square const&>(*this));
  }
};

class Circle : public AbstractShape
{
public:
  virtual std::unique_ptr<Circle> clone() {
    return std::make_unique<Circle>(static_cast<Circle const&>(*this));
  }
};

每个派生类都需要自行定义基类虚函数clone成员函数。可以使用CRTP来减少重复函数代码。

2.3.2 使用CRTP实现多态clone
template <typename Derived>
class Shape : public AbstractShape {
public:
    std::unique_ptr<AbstractShape> clone() const override {
        return std::make_unique<Derived>(static_cast<Derived const&>(*this));
    }

protected:
   // We make clear Shape class needs to be inherited
   Shape() = default;
   Shape(const Shape&) = default;
   Shape(Shape&&) = default;
};

//无需再每个派生类中实现clone函数
class Square : public Shape<Square>{};

class Circle : public Shape<Circle>{};

2.4 使用CRTP实现静态多态[2]

构建告警系统,实现可以通过不同方式发送告警事件

#include <string_view>
#include <iostream>
//定义一个Notifier基类模板
template<typename T>
class Notifier {
public:
  void AlertSMS(std::string_view msg)
  {
    impl().SendAlertSMS(msg);
  }

  void AlertEmail(std::string_view msg)
  {
    impl().SendAlertEmail(msg);
  }

private:
  T& impl(){ return static_cast<T&>(*this); }
};
//模板的类方法在调用时实例化,即使派生类T没有提供SendAlertSMS和SendAlertEmail,只有没有使用到,编译时也不会报错。

//定义一个在所有通道上发送警报的方法,实现静态多态
template<typename T>
void AlterAllChannels(Notifier<T>& notifier, std::string_view msg)
{
 notifier.AlertSMS(msg);
 notifier.AlertEmail(msg);
}
//测试
struct TestNotifier: public Notifier<TestNotifier>
{
  void SendAlertSMS(std::string_view msg){
    std::cout << "test send sms" << std::endl;
  }
  void SendAlertEmail(std::string_view msg){
    std::cout << "test send email" << std::endl;
  }
};


int main(int argc, char*argv[])
{
  TestNotifier tn;
  AlterAllChannels(tn, "testing!");
	//test send sms
	//test send email
}

三、concept

C++20新增concept概念,通过给模板类参数加入限制条件,使得代码可读性,编译报错提示更容易理解[3]。目前gcc11.3编译需要增加-std=c++20 参数使用20标准

3.1 concept的定义

//concept的定义
template<typename TImpl>
concept IsANotifier = requires(TImpl impl) {
  //定义约束,需要有AlertSMS和AlertEmail方法
  impl.AlertSMS(std::string_view{});
  impl.AlertEmail(std::string_view{});
};

使用concept对上面告警系统进行修改

//对模板方法参数进行限制,notifier需要有AlertSMS和AlertEmail
//这里的concept特性,有点类似golang中interface 特性
template<IsANotifier TImpl>
void AlterAllChannels(TImpl& notifier, std::string_view msg)
{
 notifier.AlertSMS(msg);
 notifier.AlertEmail(msg);
};

struct TestNotifier
{
  void AlertSMS(std::string_view msg){
    std::cout << "test send sms" << std::endl;
  }
  void AlertEmail(std::string_view msg){
    std::cout << "test send email" << std::endl;
  }
};

int main(int argc, char*argv[])
{
  TestNotifier tn;
  AlterAllChannels(tn, "testing!");
}

四、总结

一、通过CRTP实现静态多态,相比较于虚函数运行多态,没有查虚函数表过程,效率要比虚函数多态高。
二、可以使用concept特性来提高模板的可读性

五、参考


  1. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ↩︎ ↩︎

  2. 《C++ 20设计模式》 ↩︎

  3. https://www.cnblogs.com/chengxin1985/articles/17691936.html ↩︎