41 运行时类型识别RTTI
虚函数表,可以发现父类的虚函数在 vtable 中的索引(下标)是固定的,不会随着继承层次的增加而改变,子类新增的虚函数放在vtable的最后。如果子类有同名的虚函数覆盖了父/祖先类的虚函数,那么将使用子类的虚函数替换父类的虚函数,子类同名虚函数在子类的vtable中替换父/祖先类虚函数,其索引和父/祖先类表中的同名父类虚函数的索引是一样的。
即虚表中记录的是本类中出现的对应函数原型的形态(函数名,参数,返回值…) 的函数的地址,虚表中的索引和函数形态是一一对应的(某个索引只存这种函数形态),虚函数表中存的是 此类中出现此函数形态的虚函数地址。
当通过up_cast调用虚函数时,先取出对象中存的虚函数表指针vfptr(虚函数表指针一般就是存在对象空间的最上面,直接取对象地址其实取到的就是这个虚表指针的地址),我们根据调用的虚函数原型在虚函数表中查询是否有对应形态的函数,找到了就取出此虚函数地址然后执行。
class A{
protected:
int a1;
public:
virtual int A_virt1();
virtual int A_virt2();
static void A_static1();
void A_simple1();
};
class B{
protected:
int b1;
int b2;
public:
virtual int B_virt1();
virtual int B_virt2();
};
class C: public A, public B{
protected:
int c1;
public:
virtual int A_virt2();
virtual int B_virt2();
};
类包含了虚函数,那么该类的对象内存中还会额外增加类型信息,也即 type_info 对象。对象的虚函数指针所指虚函数表头的上方,有一个type_info对象的地址,这个type_info对象中就有保存此类的信息(类名,类编码,hash)可以唯一地确定一个类(只有类中包含了虚函数时才会启用 RTTI 机制,其他所有情况都可以在编译阶段确定类型信息)
这样在出现多态情况下 Base p = new Derive(); 编译器通过类似(p->vfptr - 1)就获得了p所指向对象的type_info,通过查看type_info就知道p是否是指向Base的后代类 类型对象,还需要通过继承链来确定type_info中的类是否是Base的后代类,对于type_info+继承链(当前类信息表+继承信息表)的具体实现方法在VS中的具体做法如下
下图中的 &类名_meta就是type_info 对象的地址
dynamic_cast中也用上面类似的RTTI方式来判断,是否可以进行down_cast(向下转型)。
typeid 经过固定次数的间接转换返回 type_info 对象,间接次数不会随着继承层次的增加而增加,对效率的影响很小,读者可以放心使用。而 dynamic_cast 运算符和异常处理不仅要经过数次间接转换,还要遍历继承链,如果继承层次较深,那么它们的性能堪忧,读者应当谨慎使用(上图的这种继承链遍历)!
42 模板简单介绍
对于函数模板而言,模板类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换。
函数模板实例化:当调用一个模板时,编译器用函数实参来推断模板实参,从而使用实参的类型来确定绑定到模板参数的类型。
template <typename T>
T add_fun(const T & tmp1, const T & tmp2){
return tmp1 + tmp2;
}
add_fun(var1, var2);// 调用时不需要指明类型,让编译器自动推导
add_fun<int>(var1, var2);//函数模板也可以指定类型
类模板:类似函数模板,类模板以关键字 template 开始,后跟模板参数列表。但是,编译器不能为类模板推断模板参数类型,需要在使用该类模板时,在模板名后面的尖括号中指明类型。(C++17以实现模板类自动推导,不需要指明类型)
template <typename T>
class Complex
{
public:
//构造函数
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
//运算符重载
Complex<T> operator+(Complex &c)
{
Complex<T> tmp(this->a + c.a, this->b + c.b);
cout << tmp.a << " " << tmp.b << endl;
return tmp;
}
private:
T a;
T b;
};
Complex<int> b(20, 30);// 指明T类型
Complex ex2(30, 40);// C++17 自动推导
函数模板和类模板的区别
函数模板是可以被重载的(类模板不能被重载),也就是说允许存在两个同名的函数模板,还可以对它们进行实例化,使它们具有相同的参数类型。
//函数模板
template <typename T>
int fun(T){
return 1;
}
//函数模板的重载
template <typename T>
int fun(T*){// 重载指针版本
return 2;
}
int fun(int i)// 重载int版本
{
return 3;
}
类模板和函数模板都可以全特化
函数全特化
template <>
int fun<int>(int i )
{
return 3;
}
就一般而言,特化是一种更通用的技巧,最主要的原因是特化可以用在类模板和函数模板上,而重载只能用于函数。
通用而言,Herb Sutter 给出了明确的建议:对函数使用重载,对类模板进行特化。 当函数重载和特化版本同时满足要求时,重载比特化优先,编译器优先调用重载版本(函数模板特化 有大坑!!!
template<typename T1, typename T2>
class A{
public:
void function(T1 value1, T2 value2){
cout<<"value1 = "<<value1<<endl;
cout<<"value2 = "<<value2<<endl;
}
};
//类模板全特化
template<>
class A<int, double>{ // 类型明确化,为全特化类
public:
void function(int value1, double value2){
cout<<"intValue = "<<value1<<endl;
cout<<"doubleValue = "<<value2<<endl;
}
};
偏特化介于普通模板和全特化之间,只存在部分的类型明确化,而非将模板唯一化。
再次划重点函数模板不能被偏特化。
template<typename T1, typename T2>
class A{
public:
void function(T1 value1, T2 value2){
cout<<"value1 = "<<value1<<endl;
cout<<"value2 = "<<value2<<endl;
}
};
template<typename T>
class A<T, double>{ // 部分类型明确化,为偏特化类
public:
void function(T value1, double value2){
cout<<"Value = "<<value1<<endl;
cout<<"doubleValue = "<<value2<<endl;
}
};
// 一般化设计
template <class T, class T1>
class TestClass
{
public:
TestClass()
{
cout<<"T, T1"<<endl;
}
};
// 针对普通指针的偏特化设计
// T指针偏特化版本 还可以进一步特化T类型 所以这是个偏特化
template <class T, class T1>
class TestClass<T*, T1*>
{
public:
TestClass()
{
cout<<"T*, T1*"<<endl;
}
};
// 针对const指针的偏特化设计
// T常指针偏特化版本 还可以进一步特化T类型 所以这是个偏特化
template <class T, class T1>
class TestClass<const T*, T1*>
{
public:
TestClass()
{
cout<<"const T*, T1*"<<endl;
}
};
对于模板、模板的特化和模板的偏特化都存在的情况下,编译器在编译阶段进行匹配时,是如何抉择的呢?从哲学的角度来说,应该先照顾最特殊的(全特化版本),然后才是次特殊的(偏特化版本),最后才是最普通的(模板版本)。编译器进行抉择也是尊从的这个道理。从上面的例子中,我们也可以看的出来,这就就不再举例说明。
43 可变参数模板简单介绍
可变参数模板:接受可变数目参数的模板函数或模板类。将可变数目的参数被称为参数包,包括模板参数包和函数参数包。
模板参数包:表示零个或多个模板参数;
函数参数包:表示零个或多个函数参数。
// 广泛用于STL中的容器类
template <typename T, typename... Args> // Args 是模板参数包
void foo(const T &t, const Args&... rest); // 可变参数模板,rest 是函数参数包
// 递归模板展开的 终止版本
template <typename T>
void print_fun(const T &t)
{
cout << t << endl; // 最后一个元素
}
// 递归展开Args模板参数
template <typename T, typename... Args>
void print_fun(const T &t, const Args &...args)
{
cout << t << " ";
print_fun(args...);
}
int main()
{
print_fun("Hello", "wolrd", "!");
return 0;
}
这一步会将args中的第一个参数赋值给void print_fun(const T &t, const Args &…args),剩余的参数赋值给args,所以每一步的递归过程中,args中参数的数量都是不断减少的。直到args中只有一个参数,再使用print_fun(args…)时直接调用void print_fun(const T &t)函数(递归基)。
44 一些小tips
include<文件名> 和 #include”文件名” 的区别:
查找文件的位置:include<文件名> 在标准库头文件所在的目录中查找(尖括号不能在当前源文件路径下寻找);#include”文件名” 在当前源文件所在目录中进行查找,如果没有;再到系统目录中查找。
使用习惯:对于标准库中的头文件常用 include<文件名>,对于自己定义的头文件,常用 #include”文件名”
45 容器迭代器使用介绍
迭代器本质是泛化的指针
迭代器按照定义方式分成以下四种。
1) 正向迭代器,定义方法如下:
容器类名::iterator 迭代器名;
支持以下操作:++p,p++,p。此外,两个正向迭代器可以互相赋值,还可以用==和!=运算符进行比较。
2) 常量正向迭代器,定义方法如下:
容器类名::const_iterator 迭代器名;
3) 反向迭代器,定义方法如下:
容器类名::reverse_iterator 迭代器名;
4) 常量反向迭代器,定义方法如下:
容器类名::const_reverse_iterator 迭代器名;
随机访问迭代器这里没提,因为我们用到迭代器的场景一般就是完整遍历某一容器
通过迭代器可以读取它指向的元素,迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。
迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:
对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。
for (int n = 0; n<5; ++n)
v.push_back(n); //push_back成员函数在vector容器尾部添加一个元素
// 0 1 2 3 4
vector<int>::iterator i; //定义正向迭代器
for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍历容器
cout << *i << " "; //*i 就是迭代器i指向的元素
}
cout << endl;
// 4 3 2 1 0
//用反向迭代器遍历容器
for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
cout << *j << " ";
双向迭代器。双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则—p和p—都是有定义的。—p使得 p 朝和++p相反的方向移动。
随机访问迭代器。随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
p+=i:使得 p 往后移动 i 个元素。
p-=i:使得 p 往前移动 i 个元素。
p+i:返回 p 后面第 i 个元素的迭代器。
p-i:返回 p 前面第 i 个元素的迭代器。
p[i]:返回 p 后面第 i 个元素的引用。
两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1
不同容器的迭代器类型
vector 随机访问
deque 随机访问
list 双向
set / multiset 双向
map / multimap 双向
stack 不支持迭代器
queue 不支持迭代器
priority_queue 不支持迭代器
vector 随机访问,可用来遍历的几种迭代器做法
vector<int> v(100); //v被初始化成有100个元素
for(int i = 0;i < v.size() ; ++i) //size返回元素个数
cout << v[i]; //像普通数组一样使用vector容器
vector<int>::iterator i;
for(i = v.begin(); i != v.end (); ++i) //用 != 比较两个迭代器
cout << * i;
for(i = v.begin(); i < v.end ();++i) //用 < 比较两个迭代器
cout << * i;
i = v.begin();
while(i < v.end()) { //间隔一个输出
cout << * i;
i += 2; // 随机访问迭代器支持 "+= 整数" 的操作
}
几个迭代器辅助函数
#include
advance(p, n):使迭代器 p 向前或向后移动 n 个元素。n>0向前 n<0向后
distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环。
iter_swap(p, q):用于交换两个迭代器 p、q 指向的内容。
46 类型萃取
什么是类型萃取
类型萃取,英文为 type traits,直译过来为类型的特点。
举个例子,无论是 int&&, int&,都算是 int 类型的变种,int就是 int&&、int& 类型的一个特点。
那么,怎么通过 int&& 获得它的特点,即 int 类型呢?
remove_reference_t
再举个例子,
is_const_v
is_const_v
即通过 is_const_v<类型> 模板,我们可以知道一个类型是否为 const修饰的类型,这就是所谓的类型萃取。
类型萃取都是在编译时进行的,在之前的笔记中的编译期类型推导中有提到
move移动函数是类型萃取的一个典型应用
template<typename T>
remove_reference<T>::type&& move(T&& t) {
return static_cast<remove_reference<T>::type &&>(t);
}
是把 t 转换为右值,无论传入时t 原本是左值还是右值。
remove_reference_t
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
//如果传入_Tp为
// 特化版本 特化了_Tp& _Tp&&的struct 对于struct中的参数_Tp是确定
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
template <class T>
typename tinySTL::remove_reference<T>::type&& move(T&& t) noexcept {
using return_type = typename tinySTL::remove_reference<T>::type&&;
return static_cast<return_type>(t);
}
然后,再把右值给强行绑上remove_reference_t
47 设计模式
设计模式有 6 大设计原则:
单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
开放封闭原则:软件实体可以扩展,但是不可修改。即面对需求,对程序的改动可以通过增加代码来完成,但是不能改动现有的代码。
里氏代换原则:一个软件实体如果使用的是一个基类,那么一定适用于其派生类。即在软件中,把基类替换成派生类,程序的行为没有变化。
依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要对实现编程。
迪米特原则:如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。
接口隔离原则:每个接口中不存在派生类用不到却必须实现的方法,如果不然,就要将接口拆分,使用多个隔离的接口。
下面介绍常见的几种设计模式:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式
简单工厂模式:主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
工厂方法模式:修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端去实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。
抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
观察者模式:定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,主题对象发生变化时,会通知所有的观察者,使他们能够更新自己。
装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成派生类更为灵活。
单例模式:保证类的实例化对象仅有一个,并且提供一个访问他的全局访问方式。
单例模式可以通过全局或者静态变量的形式实现,这样比较简单,但是这样会影响封装性,难以保证别的代码不会对全局变量造成影响。我们使用以下方式来达到封装单例的行为
默认的构造函数、拷贝构造函数、赋值构造函数声明为私有的,这样可以禁止在类的外部创建该对象;
全局访问方式使用类的静态成员函数(不会将this指针传入函数内)实现,没有参数,返回单例所拥有的堆上单例对象(对象地址使用类中静态指针变量来保存)。如下
class Singleton{
private:
static Singleton * instance;
Singleton(const Singleton& tmp){}
Singleton& operator=(const Singleton& tmp){}
// 如果单例要被继承 则构造和析构都声明为protected
Singleton(){}
~Singleton();// 禁止外部析构
public:
static Singleton* getInstance(){// 静态成员函数只能访问静态成员
if(instance == NULL){
instance = new Singleton();// 第一次调用单例对象没有创建 则将这个单例对象在堆上创建出来,如果已经有了则返回堆上单例对象的指针
}
return instance;
}
};
Singleton* Singleton::instance = NULL;
上面的这种实现方式叫做,懒汉实现直到第一次用到类的实例时才去实例化(第一次调用getInstance时单例对象才会在堆上被创建),注意上述的实现方法并没有考虑线程安全问题,当两个线程同时调用getInstance 方法,并且同时检测到 instance 是 NULL,两个线程会同时实例化对象,不符合单例模式的要求。
加锁的懒汉保证线程安全
class Singleton{
private:
static Singleton * instance;
static pthread_mutex_t mutex;
Singleton(const Singleton& tmp){}
Singleton& operator=(const Singleton& tmp){}
// 如果单例要被继承 则构造和析构都声明为protected
Singleton(){ pthread_mutex_init(&mutex, NULL); }
~Singleton();// 禁止外部析构
public:
static Singleton* getInstance(){// 静态成员函数只能访问静态成员
// 这里使用了两个 if判断语句的技术称为双检锁;好处是,只有第一次判断指针为空的时候才加锁,
// 如果第一个if判断不加则每次调用 getInstance的方法都加锁,
// 锁的开销毕竟还是有点大的。
if (instance == NULL)
{
pthread_mutex_lock(&mutex);// 加锁
if(instance == NULL)
instance = new Singleton();// 第一次调用单例对象没有创建 则将这个单例对象在堆上创建出来,如果已经有了则返回堆上单例对象的指针
pthread_mutex_unlock(&mutex);
}
return instance;
}
};
pthread_mutex_t Singleton::mutex;
Singleton* Singleton::instance = NULL;
内部静态变量的懒汉单例(C++11 线程安全)最推荐的方法
C++11中,静态局部变量是在第一次调用方法时分配内存的,C++11保证了静态局部对象是线程安全的,所以这就能实现一种完美单例模式(内部静态的懒汉模式)。如下
class Singleton{
private:
//直接定义一个同类型的静态成员变量也是被允许的
// static Singleton s;// 正确
Singleton(const Singleton& temp){}
Singleton& operator=(const Singleton& temp){}
// 如果单例要被继承 则构造和析构都声明为protected
~Singleton();// 禁止外部析构
Singleton(){}
public:
static Singleton* getInstance(){
// Lock(); // not needed after C++0x
static Singleton s;// C++11中,静态局部变量是在第一次调用方法时分配内存
// UnLock(); // not needed after C++0x
return &s;
}
};
介绍饿汉(在类定义的时候就实例化堆上单例对象)实现单例模式,和懒汉实现只有一点不同。
class Singleton{
private:
static Singleton* instance;
Singleton(const Singleton& temp){}
Singleton& operator=(const Singleton& temp){}
// 私有内嵌类
class Garbo //它的唯一工作就是在析构函数中删除Singleton的instance实例
{
public: // Garbo的构造和析构 必须声明成public,否则编译失败!!!
~Garbo()
{
// 内嵌类只能访问外部类的静态成员变量
// 内嵌类可访问外部类的私有+保护+公有函数成员
if( Singleton::instance )
{
cout<<"Garbo dtor"<<endl;
delete Singleton::instance;//instance是堆上资源而非静态资源,所以需要这样回收,如果是静态资源则和Garbo 一样在程序结束后由系统回收,我们就不需要关系写的这么麻烦
Singleton::instance = nullptr;
}
}
};
static Garbo garbo; //定义一个静态成员,程序结束时,系统会自动调用它的析构函数
// garbo是静态成员变量,其和instance构造的先后顺序依赖于静态对象的声明顺序,
// 但是析构顺序经过测试一直是garbo(成员变量)先析构,然后在garbo的析构函数中
//将instance给delete析构了(可以在内嵌类中调用delete 调用Singleton的私有析构函数)
// 这样就可以实现garbo是静态资源在程序(进程)结束时由系统回收调用garbo的析构函数,
// 在garbo的析构中实现了回收堆上的instance资源。这样我们就不需要管instance资源是否被释放,
// 只要程序结束了,instance资源自动被回收了。
// 如果单例要被继承 则构造和析构都声明为protected
~Singleton();// 禁止外部析构
Singleton(){}
public:
static Singleton* getInstance(){
return instance;
}
};
Singleton::Garbo Singleton::garbo; // 一定要初始化,不然程序结束时不会析构garbo
// 没有使用全局或静态实现单例,这里的单例是静态成员对象,需要通过类才能访问(还是实现了一层的封装,无法在全局作用域下随便访问到)
//静态成员变量的类外初始化是不受访问控制符的限制!!!!!!!
Singleton* Singleton::instance = new Singleton();
// 这里经过测试将 构造设为保护或私有都是可以正确运行的
https://zhuanlan.zhihu.com/p/83535678
工厂模式:在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。简单来说,使用了C++多态的特性,将存在继承关系的类,通过一个工厂类创建对应的子类(派生类)对象。在项目复杂的情况下,可以便于子类对象的创建。
工厂模式的实现方式可分别简单工厂模式、工厂方法模式、抽象工厂模式,每个实现方式都存在优和劣。
简单工厂
鞋厂可以指定生产耐克、阿迪达斯和李宁牌子的鞋子,需要哪个就生产哪个
// 鞋子抽象类 是具体产品类的继承的父类或实现的接口
class Shoes
{
public:
virtual ~Shoes() {}
virtual void Show() = 0;
};
// 耐克鞋子 具体产品类:工厂类所创建的对象就是此具体产品实例
class NiKeShoes : public Shoes
{
public:
void Show()
{
std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl;
}
};
// 阿迪达斯鞋子
class AdidasShoes : public Shoes
{
public:
void Show()
{
std::cout << "我是阿迪达斯球鞋,我的广告语:Impossible is nothing" << std::endl;
}
};
// 李宁鞋子
class LiNingShoes : public Shoes
{
public:
void Show()
{
std::cout << "我是李宁球鞋,我的广告语:Everything is possible" << std::endl;
}
};
简单工厂的缺点就在这里,扩展性非常差,新增产品的时候,需要去修改工厂类代码(添加枚举常量,在工厂类中创建对象的函数中添加 创建新产品分支的代码)。简单工厂模式,,需要去修改工厂类,这违背了设计模式的开闭法则。
enum SHOES_TYPE // 枚举常量代表 鞋品牌
{
NIKE,
LINING,
ADIDAS
};
// 总鞋厂 工厂类封装了创建具体产品对象的函数。
class ShoesFactory
{
public:
// 根据鞋子类型创建对应的鞋子对象
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
Shoes *CreateShoes(SHOES_TYPE type)
{
switch (type)
{
case NIKE:
return new NiKeShoes();
break;
case LINING:
return new LiNingShoes();
break;
case ADIDAS:
return new AdidasShoes();
break;
default:
return NULL;
break;
}
}
};
// 使用
// 构造工厂对象
ShoesFactory shoesFactory;
Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE);
if (pNikeShoes != NULL)
{
// 耐克球鞋广告喊起
pNikeShoes->Show();// 多态调用派生类函数
// 释放资源
delete pNikeShoes;
pNikeShoes = NULL;
}
// 从鞋工厂对象创建阿迪达斯鞋对象
Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING);
if (pLiNingShoes != NULL)
{
// 李宁球鞋广告喊起
pLiNingShoes->Show();// 多态调用派生类函数
// 释放资源
delete pLiNingShoes;
pLiNingShoes = NULL;
}
工厂方法模式
针对不同品牌的鞋子开设独立的生产线,那么每个生产线就只能生产同类型品牌的鞋
本质上就是生产的产品由同一抽象类(Shoes)得来,生产产品的工厂也由同一抽象类(ShoesFactory )得来。
// 这里的Shoes(抽象产品类:它是具体产品继承的父类(基类)),NikeShoes,AdidasShoes,LiningShoes(具体产品类:具体工厂所创建的对象,就是此类)和上面简单工厂的代码是一摸一样的这里就不写了。
// 总鞋厂
工厂方法模式抽象出了工厂类,提供创建具体产品的接口,交由子类去实现。
class ShoesFactory // 抽象工厂类:工厂方法模式的核心类,提供创建具体产品的接口,由具体工厂类实现。
{
public:
virtual Shoes *CreateShoes() = 0;
virtual ~ShoesFactory() {}
};
工厂方法模式的应用并不只是为了封装具体产品对象的创建,而是要把具体产品对象的创建放到具体工厂类实现。
不违反设计模式的开闭法则了,但每新增一个产品,就需要增加一个对应的产品的具体工厂类。相比简单工厂模式而言,工厂方法模式需要更多的类定义。
// 耐克生产者/生产链 只生产NiKeShoes类对象 具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方式
class NiKeProducer : public ShoesFactory
{
public:
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
Shoes *CreateShoes()
{
return new NiKeShoes();
}
};
// 阿迪达斯生产者/生产链 只生产AdidasShoes类对象 具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方式
class AdidasProducer : public ShoesFactory
{
public:
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
Shoes *CreateShoes()
{
return new AdidasShoes();
}
};
// 李宁生产者/生产链 只生产LiNingShoes类对象 具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方式
class LiNingProducer : public ShoesFactory
{
public:
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
Shoes *CreateShoes()
{
return new LiNingShoes();
}
};
// 使用
// ================ 生产耐克流程 ==================== //
// 鞋厂开设耐克生产线
ShoesFactory *niKeProducer = new NiKeProducer();
// 耐克生产线产出球鞋
Shoes *nikeShoes = niKeProducer->CreateShoes();
// 耐克球鞋广告喊起
nikeShoes->Show();
// 释放资源
delete nikeShoes;
delete niKeProducer;
// ================ 生产阿迪达斯流程 ==================== //
// 鞋厂开设阿迪达斯生产者
ShoesFactory *adidasProducer = new AdidasProducer();
// 阿迪达斯生产线产出球鞋
Shoes *adidasShoes = adidasProducer->CreateShoes();
// 阿迪达斯球鞋广喊起
adidasShoes->Show();
// 释放资源
delete adidasShoes;
delete adidasProducer;
抽象工厂模式
鞋厂为了扩大了业务,不仅只生产鞋子,把运动品牌的衣服也一起生产了(工厂的生产类别增多)。
本质上就是生产的产品由同一抽象类(Shoes,Clothes)得来,生产产品的工厂也由同一抽象类(Factory )得来,并且每个生产工厂抽象类要负责 对不同产品抽象类的生产(Factory 中有CreateShoes和CreateClothe两个返回不同种类产品抽象类的函数 )。每添加一种基础抽象产品都需要增加一个对应的产品的具体工厂类,这就会增大了代码的编写量。
// 基类 衣服
class Clothe
{
public:
virtual void Show() = 0;
virtual ~Clothe() {}
};
// 耐克衣服
class NiKeClothe : public Clothe
{
public:
void Show()
{
std::cout << "我是耐克衣服,时尚我最在行!" << std::endl;
}
};
// 基类 鞋子
class Shoes
{
public:
virtual void Show() = 0;
virtual ~Shoes() {}
};
// 耐克鞋子
class NiKeShoes : public Shoes
{
public:
void Show()
{
std::cout << "我是耐克球鞋,让你酷起来!" << std::endl;
}
};
// 总厂
class Factory
{
public:
virtual Shoes *CreateShoes() = 0;
virtual Clothe *CreateClothe() = 0;
virtual ~Factory() {}
};
// 耐克生产者/生产链
class NiKeProducer : public Factory
{
public:
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
Shoes *CreateShoes()
{
return new NiKeShoes();
}
Clothe *CreateClothe()
{
return new NiKeClothe();
}
};
// 使用
// ================ 生产耐克流程 ==================== //
// 鞋厂开设耐克生产线
Factory *niKeProducer = new NiKeProducer();
// 耐克生产线产出球鞋
Shoes *nikeShoes = niKeProducer->CreateShoes();
// 耐克生产线产出衣服
Clothe *nikeClothe = niKeProducer->CreateClothe();
// 耐克球鞋广告喊起
nikeShoes->Show();
// 耐克衣服广告喊起
nikeClothe->Show();
// 释放资源
delete nikeShoes;
delete nikeClothe;
delete niKeProducer;
以上三种方式,在新增产品时,要么修改工厂类,要么需新增具体的工厂类,说明工厂类的封装性还不够好。
将工厂类的封装性提高,达到新增产品时,也不需要修改工厂类,不需要新增具体的工厂类。封装性高的工厂类特点是扩展性高、复用性也高。
模板工厂
针对工厂方法模式封装成模板工厂类,那么这样在新增产品时,是不需要新增具体的工厂类,减少了代码的编写量。
可以解决产品新增时,不需要新增具体工厂类,但是缺少一个可以随时随地获取产品对象的方式。
// 基类 鞋子
class Shoes
{
public:
virtual void Show() = 0;
virtual ~Shoes() {}
};
// 耐克鞋子
class NiKeShoes : public Shoes
{
public:
void Show()
{
std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl;
}
};
// 基类 衣服
class Clothe
{
public:
virtual void Show() = 0;
virtual ~Clothe() {}
};
// 优衣库衣服
class UniqloClothe : public Clothe
{
public:
void Show()
{
std::cout << "我是优衣库衣服,我的广告语:I am Uniqlo" << std::endl;
}
};
// 抽象模板工厂和具体模板工厂(负责创建具体产品)
// 抽象模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类
template <class AbstractProduct_t>
class AbstractFactory
{
public:
virtual AbstractProduct_t *CreateProduct() = 0;
virtual ~AbstractFactory() {}
};
// 具体模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类,ConcreteProduct_t 产品具体类
template <class AbstractProduct_t, class ConcreteProduct_t>
class ConcreteFactory : public AbstractFactory<AbstractProduct_t>
{
public:
// 返回直接实现up_cast,返回的是基类指针,并且是指向派生类对象的基类指针
AbstractProduct_t *CreateProduct()
{
return new ConcreteProduct_t();
}
};
// 使用
// 构造耐克鞋的工厂对象
ConcreteFactory<Shoes, NiKeShoes> nikeFactory;// <AbstractProduct_t 抽象产品类别, ConcreteProduct_t具体产品>
// 创建耐克鞋对象
Shoes *pNiKeShoes = nikeFactory.CreateProduct();
// 打印耐克鞋广告语
pNiKeShoes->Show();
// 构造优衣库衣服的工厂对象
ConcreteFactory<Clothe, UniqloClothe> uniqloFactory;
// 创建优衣库衣服对象
Clothe *pUniqloClothe = uniqloFactory.CreateProduct();
// 打印优衣库广告语
pUniqloClothe->Show();
// 释放资源
delete pNiKeShoes;
pNiKeShoes = NULL;
delete pUniqloClothe;
pUniqloClothe = NULL;
产品注册模板类+单例工厂模板类
前面的模板工厂虽然在新增产品的时候,不需要新增具体的工厂类,但是缺少一个可以统一随时随地获取指定的产品对象的类。
把产品注册的功能封装成产品注册模板类(Registrar)。注册的产品对象保存在工厂模板类的std::unordered_map,便于产品对象的获取。
把获取产品对象的功能封装成工厂模板类。为了能随时随地获取指定产品对象,则把工厂设计成单例模式。
// 基类,产品注册模板接口类
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class IProductRegistrar
{
public:
// 获取产品对象抽象接口
virtual ProductType_t *CreateProduct() = 0;
protected:
// 禁止外部构造和析构, 子类的"内部"的其他函数可以调用
IProductRegistrar() {}
virtual ~IProductRegistrar() {}
private:
// 禁止外部拷贝和赋值操作
IProductRegistrar(const IProductRegistrar &);
const IProductRegistrar &operator=(const IProductRegistrar &);
};
// 产品注册模板类,用于创建具体产品和从工厂里注册产品
// 模板参数 ProductType_t 表示的类是产品抽象类(基类 Shoes),ProductImpl_t 表示的类是具体产品(产品种类的子类 NikeShoes)
template <class ProductType_t, class ProductImpl_t>
class ProductRegistrar : public IProductRegistrar<ProductType_t>
{
private:
string product_name;
public:
// 构造函数,用于注册产品到工厂,只能显示调用
explicit ProductRegistrar(std::string name):product_name(name)
{
// 通过工厂单例把产品注册到工厂
// 在第一次创建实际产品 注册器时 会创建静态抽象工厂单例,并将实际产品注册器登录到工厂的哈希表中
// 重复注册问题,将会导致之前的同具体产品注册器失效
if(!ProductFactory<ProductType_t>::Instance().RegisterProduct(this, name))
// 后注册的注册器无效,设置其名字为空,防止其析构时误将正在用的注册器从哈希表中删除了
name = “”;
}
// 创建具体产品对象指针
ProductType_t *CreateProduct()
{
return new ProductImpl_t();
}
//当注册器对象析构后 对应name里记录的就是野指针,还需要在注册器析构的时候将哈希表中对应的键值对给删除了
// 产品登出(删除产品) 需要保证只能在 注册器析构的时候调用这个函数
~ProductRegistrar()
{
ProductFactory<ProductType_t>::Instance().deRegisterProduct(product_name);
}
};
// 工厂模板类,用于获取和注册产品对象
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class ProductFactory
{
public:
// 获取工厂单例,工厂的实例是唯一的
static ProductFactory<ProductType_t> &Instance()
{
static ProductFactory<ProductType_t> instance;// 静态懒汉 线程安全
return instance;
}
// 产品注册
bool RegisterProduct(cosnst IProductRegistrar<ProductType_t> *registrar, cosnst std::string& name)
{
// 有点问题 当注册器对象析构后 对应name里记录的就是野指针,还需要在注册器析构的时候将哈希表中对应的键值对给删除了
// 防止重复登记同名产品,只保留第一次登记的那个注册器指针
if (m_ProductRegistry.find(name) != m_ProductRegistry.end()) {
// 后注册的注册器无效,设置其名字为空,防止其析构时误将正在用的注册器从哈希表中删除了
return false;
}
m_ProductRegistry[name] = registrar;// 键 产品名 值 产品注册器指针(产品注册器有CreateProduct可以直接生产产品)
return true;
}
// 根据名字name,获取对应具体的产品对象
ProductType_t *GetProduct(std::string name)
{
// 从哈希表找到已经注册过的产品(产品注册器),并返回产品对象
if (m_ProductRegistry.find(name) != m_ProductRegistry.end())
{
return m_ProductRegistry[name]->CreateProduct();
}
// 未注册的产品,则报错未找到
std::cout << "No product found for " << name << std::endl;
return NULL;
}
private:
// 禁止外部构造和析构
ProductFactory() {}
~ProductFactory() {}
// 禁止外部拷贝和赋值操作
ProductFactory(const ProductFactory &);
const ProductFactory &operator=(const ProductFactory &);
// 保存注册过的产品,key:产品名字 , value:产品类型
std::unordered_map<std::string, IProductRegistrar<ProductType_t> *> m_ProductRegistry;
};
对于不同的产品种类有专门的单例工厂用于生产这种产品
ProductFactory
ProductFactory<抽象产品类别>::Instance()
每种抽象产品工厂类单例的ProductFactory<抽象产品类别>::Instance()初次构造是在ProductRegistrar<抽象产品类别,具体产品>的构造函数体中,创建了静态工厂单例,并将这种产品注册器ProductRegistrar<抽象产品类别,具体产品>的指针登记到 抽象产品工厂ProductRegistrar<抽象产品类别>工厂的哈希表中[实际产品名字符串]=实际产品注册器 中,通过实际产品注册器可以实现创建实际产品对象
// 实际使用
// 产品注册器ProductRegistrar在注册阶段
// ========================== 生产耐克球鞋过程 ===========================//
// 注册产品种类为Shoes(基类),产品为NiKe(子类)到工厂,产品名为nike
ProductRegistrar<Shoes, NiKeShoes> nikeShoes("nike");// 首次创建产品注册器,在其构造函数中会创建抽象产品工厂单例,并将注册器(以指明实际产品)登录到 工厂的哈希表中[实际产品名字符串]=实际产品注册器 中,通过实际产品注册器可以实现创建实际产品对象
// ========================== 生产优衣库衣服过程 ===========================//
// 注册产品种类为Clothe(基类),产品为UniqloClothe(子类)到工厂,产品名为uniqlo
ProductRegistrar<Clothe, UniqloClothe> uniqloClothe("uniqlo");
// 从工厂获取产品种类为Clothe,名称为uniqlo的产品对象
// 从工厂获取产品种类为Shoes,名称为nike的产品对象
Shoes *pNiKeShoes = ProductFactory<Shoes>::Instance().GetProduct("nike");
Clothe *pUniqloClothe = ProductFactory<Clothe>::Instance().GetProduct("uniqlo");
// 显示产品的广告语
pNiKeShoes->Show();
// 显示产品的广告语
pUniqloClothe->Show();
// 释放资源
if (pNiKeShoes)
{
delete pNiKeShoes;
}
// 释放资源
if (pUniqloClothe)
{
delete pUniqloClothe;
}
https://www.cnblogs.com/carsonzhu/p/5770253.html
观察者模式
观察者模式:定义一种一(被观察类)对多(观察类)的关系,让多个观察对象同时监听一个被观察对象,被观察对象状态发生变化时,会通知所有的观察对象,使他们能够更新自己的状态。(这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者)
观察者模式中存在两种角色:
观察者:内部包含被观察者对象,当被观察者对象的状态发生变化时,更新自己的状态。(接收通知更新状态)
被观察者:内部包含了所有观察者对象,当状态发生变化时通知所有的观察者更新自己的状态。(发送通知)
应用场景:
当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象有待改变时,应该考虑使用观察者模式;
一个抽象模型有两个方面,其中一方面依赖于另一方面,这时可以用观察者模式将这两者封装在独立的对象中使它们各自独立地改变和复用。
这里的目标 Subject 提供依赖于它的观察者 Observer 的注册( Attach) 和注销( Detach)操作(添加删除对Subject 对象的观察者 Observer),并且提供了使得依赖于它的所有观察者同步的操作( Notify)。 观察者 Observer 则提供一个 Update 操作, 注意这里的 Observer 的 Update 操作并不在 Observer 改变了 Subject 目标状态的时候就对自己进行更新, 这个更新操作要延迟到 Subject 对象发出 Notify 通知所有Observer 进行修改(调用 Update)。
class Subject;
//抽象观察者
class Observer
{
protected:
string name;// 每个观察者特有的名字
Subject *sub;// 保存其具体观察的对象 指针
public:
Observer(string name, Subject *sub)
{
this->name = name;
this->sub = sub;
}
virtual void update() = 0;
};
//具体的观察者,看股票的
class StockObserver :public Observer
{
public:
StockObserver(string name, Subject *sub) :Observer(name, sub)
{
}
void update();// 在被观察者通知notify后做这个特别的观察者的特殊动作
};
void StockObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭股票,装做很认真工作的样子!" << endl;
}
}
//具体的观察者,看NBA的
class NBAObserver :public Observer
{
public:
NBAObserver(string name, Subject *sub) :Observer(name, sub)
{
}
void update();// 在被观察者通知notify后做这个特别的观察者的特殊动作
};
void NBAObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭NBA,装做很认真工作的样子!" << endl;
}
}
//抽象通知者
class Subject
{
protected:
list<Observer*> observers;// 登记观察自己的观察者列表
// 双向链表实现,其实用哈希表更好,观察者本身每个都有一个独一无二的name
// 可以用name做键,观察者地址做值
public:
string action;
virtual void attach(Observer*) = 0;// 登记观察自己的观察者
virtual void detach(Observer*) = 0;// 删除观察自己的观察者
virtual void notify() = 0;
};
//具体通知者,秘书
class Secretary :public Subject
{
public:
string act;
void attach(Observer *observer)// 登记观察自己的观察者
{
observers.push_back(observer);
}
void detach(Observer *observer)
{
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end())
{
if ((*iter) == observer)
{
observers.erase(iter);// 删除观察自己的观察者
}
++iter;
}
}
void notify()
{
list<Observer *>::iterator iter = observers.begin();
action = "梁所长来了!";
// 遍历登记在observers中的所有观察者,并发出自己状态改变(做出action)的通知,使所有观察者自己的状态发生对应的变化
while (iter != observers.end())
{
(*iter)->update();
++iter;
}
}
};
观察者本质,被观察对象Subject为一对多的关系在Subject中需要用容器保存登记所有正在观察他的观察者,在被观察对象的状态发生改变后,需要被观察对象Subject遍历容器依次调用观察者的update函数,来让观察者的状态根据被观察对象的变化发生对应的改变。
//使用
Subject *dwq = new Secretary(); //被观察的对象
Observer *xs = new NBAObserver("xiaoshuai", dwq);
Observer *zy = new NBAObserver("zouyue", dwq);
Observer *lm = new StockObserver("limin", dwq);
//加入观察队列
dwq->attach(xs);
dwq->attach(zy);
dwq->attach(lm);
dwq->notify();
48 关于构造和析构的访问权限
基类的构造和析构在派生类内的访问权限需要是public或protected否则无法在派生类内调用基类的构造或析构函数,会直接报错。
最后由主函数构造的派生类,其构造或析构必须是要是public的否则在 主函数内无法构造或析构,会直接报错。
标准模板库的六大组件:
1、容器
2、算法
3、迭代器
4、分配器
5、仿函数
6、适配器