C++特殊类设计(设计模式)和类型转换方式
在实际应用中,会遇到很多常见的特殊类,本篇会介绍这些类的设计方式
在这之前,虽然没有听过设计模式,但肯定接触过设计模式的例子。
比如迭代器模式,是基于面向对象三大特性之一的封装设计出来的 ,用迭代器类封装后,不暴露容器结构的情况下,用统一的方式访问修改容器中的数据
再比如适配器模式,运用了复用的思想,例如 stack 、 queue 等就是根据底层容器适配而来的
除此之外,还有工厂模式、装饰器模式、观察者模式、单例模式等等,由于大部分设计模式并不常用,因此本篇只会涉及单例模式
不能被拷贝的对象
对于一个对象的拷贝,只会发生在拷贝构造和赋值重载中,那么只需要把这两个成员方法禁用即可
class CopyBan
{
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(CopyBan) = delete;
public:
//成员方法
private:
//成员变量
};只能在堆上创建的对象
要像该对象只能在堆上被创建,可以将构造函数定义为私有,再用过统一的接口去创建它
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;
}只能在栈上创建的对象
既然只能在栈上创建,只需要把统一接口内返回的对象改为栈即可
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对象时,它是在数据段创建的,而不是在栈上,因此有缺陷:
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()方法时实例化,它最核心的思想是延时加载,从而优化服务器启动速度
饿汉举例:
将构造和析构函数私有化,防止外部调用,并禁用拷贝构造和赋值重载,就可以防止用户构造对象
//饿汉
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;
}懒汉举例:
在函数内定义局部静态变量,该变量的生命周期也是随整个程序,后续如果再次执行定义局部变量的代码,也会因为已经定义过而直接跳过。在多线程中可能存在多个线程同时获取懒汉单例的情况,因此需要注意线程安全!
//懒汉
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语言阶段,类型转换分为显式/隐式类型转换两种,隐式类型转换作用于意义相近的类型,显式类型转换(强制类型转换)作用于意义差别大的类型
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语言的显式(强制)类型转换
在转换时,<>中是要转换为的类型,()中是要转换的数据
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语言中就直接用强转)
const int ci = 0; int* pi1 = const_cast<int*>(&ci);//C++ int* pi2 = (int*)&ci;//C语言阶段
但不管是C语言阶段还是C++阶段,去除const属性后的变量,再改值也不会应用到原变量中
const int ci = 0; int* pi1 = const_cast<int*>(&ci);//C++ int* pi2 = (int*)&ci;//C语言阶段 *pi1 = *pi2 = 5; cout << *pi1 << ' ' << *pi2 << ' ' << '\n' << ci;
5 5 0
这是因为读取带const属性的变量时默认在CPU的寄存器中读取,即使内存中的值改变了,但由于读取时没有访问内存,直接从寄存器中拿,因此值没有变,这是编译器的优化机制。
要想禁用这项编译器优化,可以在定义const变量前加上 volatile 关键字
volatile const int ci = 0;
5 5 5
dynamic_cast
对于继承类中有虚函数的类,也就是多态类,若一个函数的参数是父类对象的引用/指针,父子类都可以传参,若是子类,会发生切片,这个过程是语法天然支持的(向上转换);若函数参数是子类指针/引用,若实参为父类,成功与否要看具体情况(向上转换)
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,引用转换失败时报异常
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;
}总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。


最新评论