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

 更新时间:2026年05月24日 09:57:13   作者:青瓦梦滋  
本文详细解析了单例模式、饿汉模式与懒汉模式的设计方法,并对比了C++中的static_cast、reinterpret_cast与const_cast类型转换方式,同时探讨了dynamic_cast在多态类中的应用,为开发者提供了丰富的设计模式与类型转换知识

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

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

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

再比如适配器模式,运用了复用的思想,例如 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;
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C++基于EasyX框架实现飞机大战小游戏

    C++基于EasyX框架实现飞机大战小游戏

    EasyX是针对C/C++的图形库,可以帮助使用C/C++语言的程序员快速上手图形和游戏编程。本文将利用EasyX框架实现飞机大战小游戏,需要的可以参考一下
    2023-01-01
  • opencv3/C++视频中叠加透明图片的实现

    opencv3/C++视频中叠加透明图片的实现

    今天小编就为大家分享一篇opencv3/C++视频中叠加透明图片的实现,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • C语言实现生成新春福字的示例详解

    C语言实现生成新春福字的示例详解

    这篇文章主要介绍了如何利用C语言实现生成各个字体的新春福字,再也不用担心支付宝扫福找不到图片了,感兴趣的同学可以跟随小编学习一下
    2022-01-01
  • 举例讲解C语言对归并排序算法的基础使用

    举例讲解C语言对归并排序算法的基础使用

    这篇文章主要介绍了C语言对归并排序算法的使用,归并排序算法的平均事件复杂度为(n\log n),需要的朋友可以参考下
    2016-05-05
  • 详解C/C++中低耦合代码的设计实现

    详解C/C++中低耦合代码的设计实现

    这篇文章主要为大家详细介绍了C/C++ 相关低耦合代码的设计实现,文中的示例代码讲解详细,对我们学习C++有一定的帮助,感兴趣的小伙伴可以了解一下
    2023-01-01
  • C++实现顺序排序算法简单示例代码

    C++实现顺序排序算法简单示例代码

    这篇文章主要介绍了C++实现顺序排序算法简单示例代码,对于学过C++的朋友一定不会陌生,现在重温一下这个算法,需要的朋友可以参考下
    2014-08-08
  • QT使用QChart绘制饼图

    QT使用QChart绘制饼图

    在Qt中使用QChart类可以快速绘制一个图表出来,比如折线图、饼图、柱状图等,本文就来为大家介绍一下如何利用QChart绘制简单的饼图吧
    2024-11-11
  • C语言实现冒泡排序的思路以及过程

    C语言实现冒泡排序的思路以及过程

    冒泡排序是最简单的排序方法,理解起来容易。虽然它的计算步骤比较多,不是最快的,但它是最基本的,初学者一定要掌握。本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值
    2021-09-09
  • C语言修炼之路初识指针阴阳窍 地址还归大道真下篇

    C语言修炼之路初识指针阴阳窍 地址还归大道真下篇

    指针是指向另一个变量的变量。意思是一个指针保存的是另一个变量的内存地址。换句话说,指针保存的并不是普通意义上的数值,而是另一个变量的地址值。一个指针保存了另一个变量的地址值,就说这个指针“指向”了那个变量
    2022-02-02
  • Windows程序内部运行机制实例详解

    Windows程序内部运行机制实例详解

    这篇文章主要介绍了Windows程序内部运行机制实例详解,对于学习Windows程序设计来说是非常重要的一课,需要的朋友可以参考下
    2014-08-08

最新评论