c++ advanced(7)best example to understand CRTP

CRTP

全称是curiously recurring template pattern。

在搞明白什么已经为什么需要CRTP之前,我们先假定一个场景: 我们想要用很多基于某个基类的衍生类,存入一个容器中,然后让这些衍生类做一个深拷贝,存入另一个容器中。这是比较常见的一种实际开发场景。

比如这种:

struct shape 
{
	virtual ~shape() = default;
};
struct circle:public shape
{
	int radius = 1;
};
struct rectangle:public shape
{
	int width = 1;
	int length = 2;
};

我们在函数中可能会这样写:

int main()
{
	vector<unique_ptr<shape>> source;
	vector<unique_ptr<shape>> target;
	source.emplace_back(make_unique<circle>());
	source.emplace_back(make_unique<rectangle>());
	for (auto&& p : source)
	{
		target.emplace_back(make_unique<shape>(*p));
	}
	return 0;
}

事实上,运行能通过,但是,如果我们改成这样:

using namespace std;
struct shape 
{
	virtual void message()
	{
		cout << "this is the base struct" << endl;
	};
	virtual ~shape() = default;
};
struct circle:public shape
{
	void message() final 
	{
		cout << "this is a circle" << endl;
	}
	circle(int i) :radius(i) {};
	int radius ;
};
struct rectangle:public shape
{
	void message() final
	{
		cout << "this is a rectangle" << endl;
	}
	rectangle(int w, int l) :width(w), length(l) 
	{};
	int width = 1;
	int length = 2;
};
int main()
{
	shape* s=new rectangle(1, 2);
	vector<unique_ptr<shape>> source;
	vector<unique_ptr<shape>> target;
	source.emplace_back(make_unique<circle>(1));
	source.emplace_back(make_unique<rectangle>(1,2));
	for (auto&& p : source)
	{
		p->message();
		target.emplace_back(make_unique<shape>(*p)); 
	}
	for (auto&& p : target) 
	{
		p->message();
	}
	return 0;
}

结果是:

this is a circle
this is a rectangle
this is the base struct
this is the base struct

然后我们发现其实子类根本没有实现对应的拷贝构造,因为我们调用make_unique<shape>实际上是在构造一个shape的类型,它本身并不会根据我们传入的类型不同而选择合适的派生类。最简单的解决方法如下:

using namespace std;
struct shape 
{
	virtual shape* clone() = 0;
	virtual void message()
	{
		cout << "this is the base struct" << endl;
	};
	virtual ~shape() = default;
};
struct circle:public shape
{
	shape* clone() final 
	{
		return new circle(*this);
	}
	void message() final 
	{
		cout << "this is a circle" << endl;
	}
	circle(int i) :radius(i) {};
	int radius ;
};
struct rectangle:public shape
{
	shape* clone() final
	{
		return new rectangle(*this);
	}
	void message() final
	{
		cout << "this is a rectangle" << endl;
	}
	rectangle(int w, int l) :width(w), length(l) 
	{};
	int width = 1;
	int length = 2;
};
int main()
{
	
	vector<unique_ptr<shape>> source;
	vector<unique_ptr<shape>> target;
	source.emplace_back(make_unique<circle>(1));
	source.emplace_back(make_unique<rectangle>(1,2));
	for (auto&& p : source)
	{
		p->message();
		target.emplace_back(unique_ptr<shape>(p->clone())); 
	}
	for (auto&& p : target) 
	{
		p->message();
	}
	return 0;
}

输出的结果为:

this is a circle
this is a rectangle
this is a circle
this is a rectangle

 然后如果这样的类型成千上万,明显不能够手撸,我们学过泛型编程,那么很容易想到这个方法:

struct shape 
{
	template<typename T>
	T* clone() 
	{
		return new T(*this);
	};
	virtual void message()
	{
		cout << "this is the base struct" << endl;
	};
	virtual ~shape() = default;
};

那么问题来了,在emplace_back的时候不知道选择什么类型:

int main()
{
	
	vector<unique_ptr<shape>> source;
	vector<unique_ptr<shape>> target;
	source.emplace_back(make_unique<circle>(1));
	source.emplace_back(make_unique<rectangle>(1,2));
	for (auto&& p : source)
	{
		p->message();
		target.emplace_back(unique_ptr<shape>(p->clone<>())); //???
	}
	for (auto&& p : target) 
	{
		p->message();
	}
	return 0;
}

那么好,有人说可以选择shape,我们试试:

target.emplace_back(unique_ptr<shape>(p->clone<shape>())); //shape

 输出如下,如我们所见,这根本行不通,跟我们最开始错误一样,这种克隆方式会导致信息的丢失。

this is a circle
this is a rectangle
this is the base struct
this is the base struct

 所以我们需要对T的做进一步的限制,也就是在传入的时候让它自动推断我们的T是什么,而不是用手撸的方式。

CRTP实际上就解决了这个问题,一起来学习一下:

using namespace std;
struct shape 
{
	virtual shape* clone() = 0;
	virtual void message()
	{
		cout << "this is the base struct" << endl;
	};
	virtual ~shape() = default;
};
template<typename T>
struct shapeCRTP:public shape
{
	shape* clone() final 
	{
		return new T(*static_cast<T*>(this));
	};
};
struct circle:public shapeCRTP<circle>
{
	void message() final
	{
		cout << "this is a circle" << endl;
	}
	circle(int i) :radius(i) {};
	int radius ;
};
struct rectangle :public shapeCRTP<rectangle>
{
	void message() final
	{
		cout << "this is a rectangle" << endl;
	}
	rectangle(int w, int l) :width(w), length(l) 
	{};
	int width = 1;
	int length = 2;
};

有几点要注意:

  1. 中间多了一个shapeCRTP类,派生类继承的是它而不是shape,
  2. 注意T(*static_cast<T*>(this));,需要转换为T类型,因为this本身是shapeCRTP,我们希望生成的子类是circle或者rectangle,shapeCRTP的作用仅仅是帮助我们针对clone这个函数自动绑定子类的类型信息。
  3. 在完成后不需要再指定clone的类型,因为会自动调用shapeCRTP中的clone函数,而T根据circle以及rectangle的不同已经分别指定了其类型
int main()
{
	
	vector<unique_ptr<shape>> source;
	vector<unique_ptr<shape>> target;
	source.emplace_back(make_unique<circle>(1));
	source.emplace_back(make_unique<rectangle>(1,2));
	for (auto&& p : source)
	{
		p->message();
		target.emplace_back(unique_ptr<shape>(p->clone())); //???
	}
	for (auto&& p : target) 
	{
		p->message();
	}
	return 0;
}

输出的结果为:

this is a circle
this is a rectangle
this is a circle
this is a rectangle

行,摸了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无情の学习机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值