C++特殊类设计(设计模式)和类型转换

目录

不能被拷贝的对象

只能在堆上创建的对象

只能在栈上创建的对象

单例模式

C++的类型转换

static_cast/reinterpret_cast

const_cast

dynamic_cast


在实际应用中,会遇到很多常见的特殊类,本篇会介绍这些类的设计方式

在这之前,虽然没有听过设计模式,但肯定接触过设计模式的例子。

比如迭代器模式,是基于面向对象三大特性之一的封装设计出来的 ,用迭代器类封装后,不暴露容器结构的情况下,用统一的方式访问修改容器中的数据

再比如适配器模式,运用了复用 的思想,例如stackqueue等就是根据底层容器适配而来的

除此之外,还有工厂模式、装饰器模式、观察者模式、单例模式等等,由于大部分设计模式并不常用,因此本篇只会涉及单例模式

不能被拷贝的对象

对于一个对象的拷贝,只会发生在拷贝构造和赋值重载中,那么只需要把这两个成员方法禁用即可

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;
}
相关推荐
(Charon)1 小时前
【C++/Qt】Qt 网络工具中的输入校验设计:IP、端口、URL 和空内容判断
服务器·c++·tcp/ip
liu****1 小时前
第16届国赛蓝桥杯大赛C/C++大学B组
c语言·数据结构·c++·算法·蓝桥杯
geovindu1 小时前
python: Monitor Pattern
开发语言·python·设计模式·监控模式
nazisami2 小时前
红黑树详解
数据结构·c++·面向对象·红黑树
workflower2 小时前
人工智能全球治理
大数据·人工智能·设计模式·机器人·动态规划
workflower2 小时前
AI灵活高效的智慧用能核心场景
大数据·人工智能·设计模式·机器人·动态规划
kyle~2 小时前
RTPS(Real-Time Publish-Subscribe)---DDS的传输协议
c++·机器人·ros2
TIEM_692 小时前
C++ vector容器全面解析:从入门到精通
开发语言·c++
Irissgwe2 小时前
c++多态
开发语言·c++·多态