C++设计模式:CRTP

C++设计模式:CRTP

CRTP(curiously recurring template pattern)是C++的一种设计模式,能够实现静态多态。静态多态相比动态多态,运行时效率更高,常用于基础库的开发。一个类Derived从一个模板类Base推导而来,而这个模板类使用Derived作为模板参数,这是CRTP最常见的模式。可以看出,一个模板类使用了一个不完整的类型作为模板参数。–参考维基百科



1 历史

CRTP这个名字由Jim Coelien在1995年创建,源于他看过一些早期的C++模板代码,包括Timothy Budd创造的多重泛型编程语言Leda。
微软也独立的使用CRTP开发Active Template Library(ATL),时间也是在1995年。Jan FalKin意外地使用派生类推导基类,Christian Beaumont第一个看到Jan的代码,并且认为这个不可能在微软的编译器上进行编译,然而出人意料的是,代码竟然编译通过了。所以Christian就基于这个意料之外的错误设计了ATL和Windows Template Library(WTL)。

2 通用形式

使用这种形式最为常见的是静态多态和Andrei Alexandrescu 在现代C++中使用的元编程技术。

// CRTP
template <class T>
class Base
{
	// Base类里面的方法可以使用template去访问Derived的成员
};
class Derived : public Base<Derived>
{
	// 实现Derived的方法
};

2.1 静态多态

模板基类可以直接使用其派生类的方法,静态方法可以直接使用,非静态方法可以转换成派生类指针。在下面的例子中,Base::implementation()尽管在struct Derived存在之前申明,编译器并不会报错,因为模板的实例化是在调用它的地方,在那时Derived::implementation()已经实现了。这种技术实现了与虚函数类似的效果,而没有动态多态那么消耗巨大,Windows的ATL和WTL很好的运用了这个特性。

// 静态多态
template <class T>
class Base
{
	void interface(void)
	{
		static_cast<T *>(this)->implementation();
	}

	static void staticFunc(void)
	{
		T::staticSubFunc(void);
	}
};
class Derived : public Base<Derived>
{
	void implementation(void);
	static void staticSubFunc(void);
};

展开讨论一下CRTP实现的功能。假设有一个基类,没有定义虚函数,无论基类何时调用另外一个成员函数,它都只会调用自己类中实现的成员函数。假设从一个基类派生出一个派生类,派生类继承了基类的所有成员变量和除构造函数析构函数以外的所有成员函数。如果派生类调用继承基类的一个成员函数,该成员函数调用另外一个成员函数,这个成员函数也只能是基类中的实现。
然而,当使用了CRTP后,派生类重写的成员函数可以被基类成员函数调用,这种行为是在编译时决定的。CRTP模拟了虚函数的行为,但是在性能上却有显著的优势。

2.2 对象计数器

对象计数器的目的是统计某个类有多少个对象被创建和销毁,使用CRTP可以很容易实现。如下代码所示,每次创建一个Orange类的对象,Counter构造函数被调用,增加了创建的和活跃的对象计数。每次销毁一个Orange对象,活跃的Orange对象计数减一。有一个问题是,Orange的计数会与Apple的计数相互混在一起吗?答案是不会的,因为template机制会为每一个类都创建一个计数Counter。

template <typename T>
struct Counter
{
	static inline int objectsCreated = 0;
	static inline int objectsAlive = 0;
	counter()
	{
		++objectsCreated;
		++objectsAlive;
	}
	// 用一个已有对象初始化一个新对象时也要计数加1
	counter(const Counter&)
	{
		++objectsCreated;
		++objectsAlive;
	}
protected:
	~counter()
	{
		--objectsAlive();
	}
};

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

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

2.3 多态链

一个些操作符或者函数可以形成调用链,如几个数连加等,如果这种链式调用有多态行为呢?

2.3.1 一个失败的例子

首先,不用CRPT,来看一个失败的例子。

class Printer
{
public:
	Printer(ostream& pStream) : mStream(pStream) {}

	template<typename T>
	Printer& print(T&& t) {mStream << t; return *this;}
	template<typename T>
	Printer& printLn(T&& t) {mStream << t << endl; return *this;}
private:
	ostream& mStream;
}

类本身的链式调用是很容易实现的,这里还不涉及多态。

// 可以运行
Printer(myStream).printLn("hello").println(500);

但是,如果以Printer为基类构建一个派生类,那么派生类将不能构成链式调用。

class CoutPrinter : public Printer
{
public:
	CoutPrinter() : Printer(cout) {}
	CoutPrinter& SetConsoleColor(Color c)
	{
		return *this;
	}
};

// 如下的使用方式将会失败,原因是Printer类返回的是一个Printer类型实例,只能调用Printer类里面的函数,不能调用CoutPrinter里面的函数。
CoutPrinter().print("hello").SetConsoleColor(Color.red).println("Printer!");

2.3.2 使用CRTP解决这个问题

上一节不能构成链式调用的原因是返回类型不是派生类,要解决这个问题就需要返回一个派生类类型的实例。办法就是使用static_cast<ConcretePrinter&> 进行类型的强制转换。而派生类类型通过CRTP传入。

tempalte <typename ConcretePrinter>
class Printer
{
public:
	Printer(ostream& pStream) : mStream(pStream) {}

	template<typename T>
	ConcretePrinter& print(T&& t)
	{
		mStream << t;
		return static_cast<ConcretePrinter&>(*this);
	}
	template<typename T>
	ConcretePrinter& printLn(T&& t)
	{
		mStream << t << endl;
		return static_cast<ConcretePrinter&>(*this);
	}
private:
	ostream& mStream;
}

class CoutPrinter : public Printer<CoutPrinter>
{
public:
	CoutPrinter() : Printer(cout) {}
	CoutPrinter& SetConsoleColor(Color c)
	{
		return *this;
	}
};

// 使用
CoutPrinter().print("hello").SetConsoleColor(Color.red).printLn("Printer!");

当然,CRTP还可以替代多态的复制构造,这里就不详细介绍了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值