目录
在实际应用中,会遇到很多常见的特殊类,本篇会介绍这些类的设计方式
在这之前,虽然没有听过设计模式,但肯定接触过设计模式的例子。
比如迭代器模式,是基于面向对象三大特性之一的封装设计出来的 ,用迭代器类封装后,不暴露容器结构的情况下,用统一的方式访问修改容器中的数据
再比如适配器模式,运用了复用 的思想,例如stack 、queue等就是根据底层容器适配而来的
除此之外,还有工厂模式、装饰器模式、观察者模式、单例模式等等,由于大部分设计模式并不常用,因此本篇只会涉及单例模式
不能被拷贝的对象
对于一个对象的拷贝,只会发生在拷贝构造和赋值重载中,那么只需要把这两个成员方法禁用即可
cpp
class CopyBan
{
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(CopyBan) = delete;
public:
//成员方法
private:
//成员变量
};
只能在堆上创建的对象
要像该对象只能在堆上被创建,可以将构造函数定义为私有 ,再用过统一的接口去创建它
cpp
class HeapOnly
{
HeapOnly(const HeapOnly&) = delete;//防止拷贝
public:
static HeapOnly* HeapGet()
{
return new HeapOnly;
}
private:
HeapOnly()//构造函数私有化
{
}
};
int main()
{
HeapOnly h1;//报错
HeapOnly* h2 = HeapOnly::HeapGet();
HeapOnly h3 = *h2;//报错
return 0;
}
只能在栈上创建的对象
既然只能在栈上创建,只需要把统一接口内返回的对象改为栈即可
cpp
class StackOnly
{
public:
static StackOnly StackGet()
{
return StackOnly();
}
private:
StackOnly()
{
}
};
int main()
{
StackOnly s1 = StackOnly::StackGet();
StackOnly* s2 = new StackOnly;//报错
return 0;
}
还有一种方法是禁用类的专属operator new ,但如果定义static对象 时,它是在数据段创建的,而不是在栈上,因此有缺陷:
cpp
class StackOnly
{
void* operator new(size_t size) = delete;
public:
private:
};
int main()
{
StackOnly s1;
StackOnly* s2 = new StackOnly;//报错
static StackOnly s3;//不报错
return 0;
}
单例模式
对于某些类,只应该有一个实例化对象 ,这就称为单例
单例模式有两种实现方式:
- 饿汉模式:类在加载时就创建实例,例如全局变量就可以理解为饿汉模式(定义时就初始化)
- 懒汉模式:类在第一次使用时再创建实例,一般为调用getInstance()方法时实例化,它最核心的思想是延时加载,从而优化服务器启动速度
饿汉举例:
将构造和析构函数私有化 ,防止外部调用,并禁用拷贝构造和赋值重载,就可以防止用户构造对象
cpp
//饿汉
template<class T>
class singletonHungry
{
private:
singletonHungry()
{
cout << "将构造函数私有化\n";
}
~singletonHungry()
{
cout <<"将析构函数私有化\n";
}
//防止使用拷贝构造,operator=
singletonHungry(const singletonHungry&) = delete;
void operator=(const singletonHungry&) = delete;
public:
static singletonHungry<T>* getInstance()//static表示该方法属于类本身,不属于具体对象
{
return &data;
}
private:
static singletonHungry<T> data;//唯一对象
};
template<class T>
singletonHungry<T> singletonHungry<T>::data;//静态成员需再类外声明
int main()
{
singletonHungry<int>* t = singletonHungry<int>::getInstance();
return 0;
}
懒汉举例:
在函数内定义局部静态变量 ,该变量的生命周期也是随整个程序,后续如果再次执行定义局部变量的代码,也会因为已经定义过而直接跳过。在多线程中可能存在多个线程同时获取懒汉单例的情况,因此需要注意线程安全!
cpp
//懒汉
template<class T>
class singletonLazy
{
private:
singletonLazy()
{
cout << "将构造函数私有化\n";
}
~singletonLazy()
{
cout <<"将析构函数私有化\n";
}
//防止使用拷贝构造,operator=
singletonLazy(const singletonLazy&) = delete;
void operator=(const singletonLazy&) = delete;
public:
static singletonLazy<T>& getInstance()//static表示该方法属于类本身,不属于具体对象
{
static singletonLazy<T> data;//C++11起,局部静态变量初始化是线程安全的
return data;
}
private:
//成员
};
int main()
{
singletonLazy<int>& sl = singletonLazy<int>::getInstance();
return 0;
}
饿汉和懒汉模式的区别:
- 懒汉模式需要考虑线程安全问题,实现相对复杂;而饿汉模式不需要
- 懒汉模式是一种懒加载,需要时才会初始化创建对象,不会影响程序的启动;饿汉模式在程序启动阶段就初始化创建对象,会导致程序启动慢
- 如果有多个单例类,有B依赖于A的依赖关系,就必须要先创建A,才能创建B,此时不能用饿汉,因为饿汉不能确定创建的顺序,需要用懒汉模式手动控制
- 若单例类中使用了线程或动态库,由于饿汉模式时类是在main函数之前创建,此时不能创建线程,并且用不了动态库,只能用懒汉模式
C++的类型转换
static_cast/reinterpret_cast
在C语言阶段,类型转换分为显式/隐式类型转换 两种,隐式类型转换作用于意义相近 的类型,显式类型转换(强制类型转换)作用于意义差别大的类型
cpp
int i = 1;
double d = 1.5;
i = d;//隐式类型转换
int* pi = nullptr;
pi = i;//报错
pi = (int*)i;//显式类型转换
C++不仅兼容C阶段的显式/隐式类型转换,还有自己的一套转换规范:
- **static_cast<>()**对应C语言的隐式类型转换
- **reinterpret_cast<>()**对应C语言的显式(强制)类型转换
在转换时,<>中是要转换为的类型,()中是要转换的数据
cpp
int i = 1;
double d = 1.5;
i = static_cast<int>(d);//隐式类型转换
int* pi = nullptr;
pi = i;//报错
pi = reinterpret_cast<int*>(i);//显式类型转换
C++的规范可以让可读性更强
const_cast
const_cast<>() 用于++将const变量在转换时去除const属性++(在C语言中就直接用强转)
cpp
const int ci = 0;
int* pi1 = const_cast<int*>(&ci);//C++
int* pi2 = (int*)&ci;//C语言阶段
但不管是C语言阶段还是C++阶段,去除const属性后的变量,再改值也不会应用到原变量中
cpp
const int ci = 0;
int* pi1 = const_cast<int*>(&ci);//C++
int* pi2 = (int*)&ci;//C语言阶段
*pi1 = *pi2 = 5;
cout << *pi1 << ' ' << *pi2 << ' ' << '\n' << ci;
bash
5 5
0
++这是因为读取带const属性的变量时默认在CPU的寄存器中读取,即使内存中的值改变了,但由于读取时没有访问内存,直接从寄存器中拿,因此值没有变,这是编译器的优化机制。++
要想禁用这项编译器优化,可以在定义const变量前加上volatile关键字
cpp
volatile const int ci = 0;
cpp
5 5
5
dynamic_cast
对于继承类中有虚函数的类,也就是多态类 ,若一个函数的参数是父类对象的引用/指针 ,父子类都可以传参 ,若是子类,会发生切片 ,这个过程是语法天然支持的(向上转换);若函数参数是子类指针/引用,若实参为父类,成功与否要看具体情况(向上转换)
cpp
class Parent
{
public:
virtual void fun()
{
}
int _p;
};
class Child : public Parent
{
public:
int _c;
};
void funcast(Parent* p)
{
Child* c = (Child*)p;
c->_c = 1;
c->_p = 2;
cout << c->_c << ' ' << c->_p << '\n';
}
int main()
{
Parent p;
Child c;
funcast(&p);//异常
funcast(&c);//正常运行
return 0;
}
dynamic_cast<>() 用于多态间的类型转换,它可以取代上面强制类型转换的过程,并且指针转换失败时返回nullptr ,引用转换失败时报异常
cpp
void funcast(Parent* p)
{
Child* c = dynamic_cast<Child*>(p);
if(c != nullptr)
{
c->_c = 1;
c->_p = 2;
cout << c->_c << ' ' << c->_p << '\n';
}
}
int main()
{
Parent p;
Child c;
funcast(&p);//不会输出,但不报错
funcast(&c);//正常运行
return 0;
}