模板
场景 1:产品类共享
假设有很多产品类,这些类在很多地方被使用到,需要共享。
同时为了后续方便支持多线程,又不能采用单例模式(或者是静态变量)来设计。
由于是共享,所以最节约资源的方式是在用之前初始化好,在用到时共享。
这时就需要类使用者来初始化,类很多,使用体验极差。
(
比如这样,有一堆处理类:
int main()
{
//假设 make_shared_HandlerXXX() 是工厂函数;
HandlerA a = make_shared_HandlerA();
HandlerB b = make_shared_HandlerB();
HandlerC c = make_shared_HandlerC();
. . .
func_1(a,b);
func_2(b,c);
func_3(a,c);
func_4(a,b,c);
}
如果能像下面这样,会很舒服:
int main()
{
Container handlers= make_shared_handler_container();
func_1(handlers);
func_2(handlers);
func_3(handlers);
}
是的,越无脑越好。具体应该实例化哪些处理类,应该由这些处理类的直接使用者来判断,这样能减轻类使用者的负担。
)
我们的目标就是两个:1. 共享,2. 方便
解决
首先我们需要实例化一群类,关键词有两个:实例化、一群。
很容易就会想到用模板来解决。
初次尝试:
class Handlers {
public:
template<typename t>
shared_ptr<T> get_shared_handler() {
static map<type_index, T> handlers;
try {
return handlers.at (type_index (typeid (T))));
} catch (out_of_range e) {
handlers.insert ({type_index (typeid), make_shared<T>(); });
return handlers.at (T);
}
}
};
这种不同类型,我们很容易就想到RTTI
、尝试type_index
。再配合map
,似乎能满足要求:实例化一个Handlers
就能得到一个处理类的容器,在Handlers
析构后处理类随着析构。
然而实测中,Handlers
的成员函数中的静态变量static map<type_index, T> handlers
并不会随之析构,而是似乎一直存在。
而且,我们不可能在类中定义一个这样的类成员变量:map<type_index, T> handlers
,因为不支持,变量的定义不能写模板参数。
怎么办?
二次尝试
那么就只能从类模板上下手了,
template<typename T>
class Handlers_v2 {
public:
shared_ptr<T>get_shared_handler();
private:
shared_ptr<T> handler;
};
可是这样,没什么区别,因为用户还是要手动实例化一堆类,只是用的代码不一样罢了。
解决方案
模板类的问题在于实例的分散(还是要手动实例化),但是可以管理资源;
而成员模板函数的问题在于无法管理资源,但是可以自动推导;
将两者结合来尝试解决。
模板基类 + 普通子类(模板方法)。
template<typename T>
class shared_handler {
protected:
shared_ptr<T> get_shared_handler() {
cout << "类型: " << typeid (T).name() << endl;
if (this->handler)
handler = make_shared<T>();
return handler;
}
private:
shared_ptr<T> handler;
};
class shared_handler_container : public shared_handler<int>, shared_handler<float>, shared_handler<string> {
public:
template<typename T>
shared_ptr<T> get_shared_handler() {
return this->shared_handler<T>::get_shared_handler();
}
};
这里关键就是用模板函数来获取调用者传入的模板参数,然后将这个模板参数传递给模板基类,从而调用指定模板基类的方法。
使用时:
#include <iostream>
#include <memory>
#include <typeindex>
#include <map>
using namespace std;
template<typename T>
class shared_handler {
protected:
shared_ptr<T> get_shared_handler() {
cout << "类型: " << typeid (T).name() << endl;
if (this->handler)
handler = make_shared<T>();
return handler;
}
private:
shared_ptr<T> handler;
};
class shared_handler_container : public shared_handler<int>, shared_handler<float>, shared_handler<string> {
public:
template<typename T>
shared_ptr<T> get_shared_handler() {
return this->shared_handler<T>::get_shared_handler();
}
};
int main (void)
{
shared_handler_container container;
container.get_shared_handler<int>();
container.get_shared_handler<float>();
container.get_shared_handler<string>();
}
打印结果:
类型: i
类型: f
类型: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
总结
这样,我们就无需用户在使用时写一行一行的make_shared_XXX()
工厂函数。
只需要实例化一个container
即可,这个容器可以装不同的类型。(这里不一定非得是handler
,可以是各种产品类型。)
这样,对不同的函数 / 消费类,只需要传给它一个容器就行了,然后在实现中通过get_shared_XXX
来获取共享资源。而且只在需要时实例化类。需要的话,也可以给它加上setter
,和getter
是同样的实现思路。
即使后续需要使用多线程,我们也只需要简单地实例化多份容器即可。
当然,缺点:
- 容器类需要继承一大堆产品类,
- 问题或许可以通过抽象的方法来进一步改善。
- 传参时冗余
- 有得有失,这是不可避免的。 改善方法是将相同类的资源,或者大概率联合使用的资源放在一个容器中实现。
改进版
加入setter
。同时,改变使用方式,
#include <iostream>
#include <memory>
using namespace std;
template<typename T>
class shared_object {
protected:
shared_ptr<T> get_object() {
if (this->object)
object = make_shared<T>();
return object;
}
void set_object (const T &object) {
this->object = make_shared<T> (object);
}
void set_object (const T &&object) {
this->object = make_shared<T> (std::move (object));
}
private:
shared_ptr<T> object;
};
class shared_object_container {
public:
template<typename T>
shared_ptr<T> get_object() {
return this->shared_object<T>::get_object();
}
template<typename T>
void set_object (const T &object) {
this->shared_object<T>::set_object (object);
}
template<typename T>
void set_object (const T &&object) {
this->shared_object<T>::set_object (object);
}
};
上面是不变部分,shared_object<>
是类型外壳,shared_object_container
是类型容器。
注意,我们实现两个版本的setter
,对于将亡值,使用右值引用 + std::move()
,避免不必要的拷贝。
下来,初次尝试:
class BuiltInContainer
public shared_object_container,
public shared_object<int>,
public shared_object<float>,
public shared_object<char> {
};
int main (void)
{
BuiltInContainer container;
container.get_object<int>();
}
可是这样并不行。
解决方案
使用可变参数模板来多重继承,并配合虚继承来减少递归继承带来的冗余,
#include <iostream>
#include <memory>
using namespace std;
template<typename T>
class shared_object {
protected:
shared_ptr<T> __get__() {
return object;
}
void __set__ (const T &object) {
*this->object = object;
}
void __set__ (const T &&object) {
*this->object = std::move (object);
}
private:
shared_ptr<T> object = make_shared<T>();
};
template<typename ...Args>
class shared_object_container {
};
template<typename ObjType, typename... Args>
class shared_object_container<ObjType, Args...> :
virtual public shared_object<ObjType>,
virtual public shared_object_container<Args...> {
public:
template<typename T>
shared_ptr<T> get() {
return this->shared_object<T>::__get__();
}
template<typename T>
void set (const T &object) {
this->shared_object<T>::__set__ (object);
}
template<typename T>
void set (const T &&object) {
this->shared_object<T>::__set__ (object);
}
};
int main (void)
{
shared_object_container<int, float, double, string> container;
int *a = container.get<int>().get();
cout << *a << endl;
container.set<int> (9);
cout << *a << endl;
}
如果资源较大,我们不希望它一下子就全初始化,可以让它延后(也可以将初始化时机作为一个开关):
#pragma once
#include <memory>
namespace YQ {
using std::shared_ptr;
using std::make_shared;
template<typename T>
class shared_object {
protected:
shared_ptr<T> __get__() {
if (!this->object)
this->object = make_shared<T>();
return object;
}
void __set__ (const T &object) {
if (!this->object)
this->object = make_shared<T> (object);
else
* this->object = object;
}
void __set__ (const T &&object) {
if (!this->object)
this->object = make_shared<T> (std::move (object));
else
* this->object = std::move (object);
}
private:
shared_ptr<T> object;
};
template<typename ...Args>
class shared_container {
};
template<typename ObjType, typename... Args>
class shared_container<ObjType, Args...> :
virtual public shared_object<ObjType>,
virtual public shared_container<Args...> {
public:
template<typename T>
shared_ptr<T> get() {
return this->shared_object<T>::__get__();
}
template<typename T>
void set (const T &object) {
this->shared_object<T>::__set__ (object);
}
template<typename T>
void set (const T &&object) {
this->shared_object<T>::__set__ (object);
}
};
}
考虑到实际中使用多重容器、指针时复杂度过高,我们将返回智能指针改为返回引用:
#pragma once
#include <memory>
namespace YQ {
using std::shared_ptr;
using std::make_shared;
template<typename T>
class shared_object {
protected:
T &__get__() {
return *object;
}
void __set__ (const T &object) {
* this->object = object;
}
void __set__ (const T &&object) {
* this->object = std::move (object);
}
private:
shared_ptr<T> object = make_shared<T>();
};
template<typename ...Args>
class shared_container {
};
template<typename ObjType, typename... Args>
class shared_container<ObjType, Args...> :
virtual public shared_object<ObjType>,
virtual public shared_container<Args...> {
public:
template<typename T>
T &get() {
return this->shared_object<T>::__get__();
}
template<typename T>
void set (const T &object) {
this->shared_object<T>::__set__ (object);
}
template<typename T>
void set (const T &&object) {
this->shared_object<T>::__set__ (object);
}
};
}
类型擦除:
https://www.artima.com/articles/on-the-tension-between-object-oriented-and-generic-programming-in-c
泛型抽象工厂
https://www.codenong.com/cs106602106/