【C++】22.单例模式+类型转换

1.单例模式

1°定义

之前已经学过一些设计模式

迭代器模式 -- 基于面向对象三大特性之一的 封装设计出来的 用一个迭代器类封装以后

不暴露容器结构的情况下 统一的方式访问修改容器中的数据

适配器模式 -- 体现的是一种复用

还有一些常见的设计模式如:工厂模式 装饰器模式 观察者模式 单例模式...

单例模式:一个类只能在全局(进程中)只有一个实例对象

什么场景下会使用?

比如一个进程中有一个内存池 进程中的多线程需要内存都要到这个内存池中取 那么这个内存池的类就可以设计单例模式

类的名称是随着你的场景给的 比如你的是内存池 那么你就定义成MemoryPool

2°懒汉模式

懒汉模式--第一次获取对象时 再创建对象

#include <iostream>
#include <thread>
#include <vector>
#include <Windows.h>
#include <mutex>
using namespace std;

namespace Lazy_Man
{
    //懒汉模式--第一次获取对象时 再创建对象
    class Singleton
    {
    public:
        static Singleton* GetInstance()
        {
            //Sleep(1000);//增加没加锁时出现线程不安全的条件(2个以上线程同时过了判断条件)
            //如果有两个线程同时new 最后就不是只有一个对象了
            //加锁解决
            //_mtx.lock();
            if (_pinst == nullptr)//后一波线程来的时候就直接返回了
            {
                unique_lock<mutex> lock(_mtx);
                //出作用域后解锁 但后面还有代码不想保护 添加{}局部域 
                //加锁只用保护第一次 需要优化
                //只要_pinst已经指向了new出来的实例对象 就不需要加锁了
                //可以分析出现在的代码 存在优化空间
                //双检查即可
                if (_pinst == nullptr)
                {
                    _pinst = new Singleton;
                }
            }
            //_mtx.unlock();

            //第一次给一个值
            //后面每次获取的对象就都是一样的
            return _pinst;
        }

        static void DelInstance()
        {
            unique_lock<mutex> lock(_mtx);
            delete _pinst;
            _pinst = nullptr;
        }

    private:
        Singleton()
        {}

        Singleton(const Singleton& s) = delete;

        static Singleton* _pinst;//静态的 每次过来都会是第一次的对象
        static mutex _mtx;
    };

    //1.如果要手动释放单例对象 可以调用DelInstance
    //2.如果需要程序结束时 正常释放单例对象 可以加入下面的设计
    class GC
    {
    public:
        ~GC()
        {
            Singleton::DelInstance();
        }
    };

    static GC gc;
    //定义一个全局的静态对象
    //main函数结束后会调用析构函数

    Singleton* Singleton::_pinst = nullptr;
    mutex Singleton::_mtx;
}
int main()
{
    //Singleton s1;
    //Singleton s2;
    //------------
    //cout << Singleton::GetInstance() << endl;
    //cout << Singleton::GetInstance() << endl;
    //cout << Singleton::GetInstance() << endl;
    //---------------
    //拷贝构造出来的是新的对象 拷贝构造也要禁掉
    //Singleton copy(*Singleton::GetInstance());
    
    vector<std::thread> vthreads;
    int n = 10;
    for (int i = 0; i < n; ++i)
    {
        vthreads.push_back(std::thread([]()
        {
            //cout << std::this_thread::get_id() << ":";
            cout << Lazy_Man::Singleton::GetInstance() << endl;
        }));
    }

    for (auto& t : vthreads)
    {
        t.join();
    }
    
    //有线程安全的问题
    //加锁

    return 0;
}

 

3°饿汉模式

namespace Hungry_Man
{
    //饿汉模式 一开始(main函数之前)就创建对象
    class Singleton
    {
    public:
        static Singleton* GetInstance()
        {
            return &_inst;
        }

        Singleton(const Singleton&) = delete;
    private:
        Singleton()
        {}

        static Singleton _inst;
    };

    Singleton Singleton::_inst;
    //static对象是在main函数之前创建的 这回只有主线程 所以不存在线程安全问题

    int test()
    {
        //Singleton s1;
        //Singleton s2;
        //------------
        //cout << Singleton::GetInstance() << endl;
        //cout << Singleton::GetInstance() << endl;
        //cout << Singleton::GetInstance() << endl;
        //---------------
        //拷贝构造出来的是新的对象 拷贝构造也要禁掉
        //Singleton copy(*Singleton::GetInstance());

        vector<std::thread> vthreads;
        int n = 10;
        for (int i = 0; i < n; ++i)
        {
            vthreads.push_back(std::thread([]()
                {
                    //cout << std::this_thread::get_id() << ":";
                    cout << Singleton::GetInstance() << endl;
                }));
        }

        for (auto& t : vthreads)
        {
            t.join();
        }

        //有线程安全的问题
        //加锁

        return 0;
    }
}

int main()
{
    Hungry_Man::test();
    //一开始就创建好了 没有线程安全问题
    return 0;
}

4°总结

总结对比一下饿汉和懒汉的区别

  • 懒汉模式需要考虑线程安全和释放的问题 实现相对复杂 饿汉模式不存在以上问题 实现简单
  • 懒汉是一种懒加载模式 需要时再初始化对象 不会影响程序的启动 饿汉模式则相反 程序启动阶段就创建初始化实例对象 会导致程序启动慢 影响体验
  • 如果有多个单例类 假设有依赖关系(B依赖A) 要求A单例先创建初始化 B再启动 那么就不能用饿汉 因为无法保证初始化顺序 这个用懒汉我们就可以手动控制

总结一下:实际中懒汉模式还是更实用一些

2.类型转换

1°C/C++

int main()
{
    int i = 1;
    double d = 8.88;
    i = d;//类型转换 C语言支持相近类型的隐式类型转换(相近类型 也就是意义相似的类型)
    cout << i << endl;

    int* p = nullptr;
    p = (int*)i;//C语言支持相近类型的强制类型转换(不相近类型 也就是意义差别很大的类型)
    cout << p << endl;

    //C++ 兼容C语言留下来的隐式转换和显示转换 但是C++觉得C语言做得不规范 C++想兼容一下
    //引入了四种
    //static_cast,reinterpret_cast,const_cast,dynamic_cast
    
    d = static_cast<double>(i); //对应C语言隐式类型转换(相近类型)
    p = reinterpret_cast<int*>(i);//对应C语言大部分强制类型转换(不想近类型)
    
    //const int ci = 10;//加volatile可防止优化const 
    volatile const int ci = 10;
    //int* pi = (int*)&ci;//C语言
    int* pi = const_cast<int*>(&ci);//对应C语言强制类型转换中去掉const属性(不相近类型)
    *pi = 20;
    cout << *pi << endl;//20
    cout << ci << endl;//10 这里的打印10是因为ci存储的内存被改了 但是ci被放进了寄存器 这里去寄存器中取
    //还是19 本质是由于编译器对const对象存取优化机制导致
    //const变量先放进寄存器 内存当中改成了20 但取ci的时候是去寄存器里取得 所以还是10
    //想要禁止编译器做这个优化 每次都到内存中取值 就把volatile加上
    return 0;
}

static_cast、reinterpret_cast、const_cast、dynamic_cast

2°static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用st

atic_cast,但它不能用于两个不相关的类型进行转换

对应隐式类型转换

3°reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型

转换为另一种不同的类型

对应强制类型转换

4°const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值

去掉const属性

5°dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)

向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)

向下转型:父类对象指针/引用-子类指针/引用(用dynamic_cast转型是安全的)

注意:

  1. dynamic_cast只能用于含有虚函数的类
  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A
{
public:
    virtual void f()
    {}

protected:
    int _a;
};

class B :public A
{
protected:
    int _b;
};

void f_cast(A* pa)
{
    //如果想区分pa是指向父类还是子类对象?
    //B* pb = (B*)pa;
    //强转可能出问题
    //如果pa指向的是父类对象  
    //只会多看4个字节
    //但看不到_b
    B* pb = dynamic_cast<B*>(pa);
    //如果pa指向子类对象 则转换成功
    //如果pa指向父类对象 则转换失败 返回nullptr
    if (pb != nullptr)
    {
        cout << "转换成功:pa指向子类对象" << endl;
        //pb是指向子类 pa也指向子类 是可以转换的
    }
    else
    {
        cout << "转换失败:pa指向父类对象" << endl;
    }
}


int main()
{
    A a;
    B b;

    //C++子类对象可以赋值给父类的对象 指针 引用
    //这个过程会发生切片 这个过程是语法天然支持的 这个叫向上转换 都可以成功
    //如果是父类的指针或者引用 传给子类的指针 这个过程叫向下转换 这个过程有可能能成功
    //要看具体情况
    //最后需要注意的是:dynamic_cast向下转换只能针对继承中的多态类型(父类必须包含虚函数)
     //dynamic_cast如何识别父类的指针是指向父类对象还是子类对象的呢?
    //dynamic_cast的原理:dynamic_cast通过去找虚表的上方存储的标识信息
    //来判断指向父类对象还是子类对象
    A* pa = &a;
    f_cast(pa);
    pa = &b;
    f_cast(pa);

    return 0;
}

 

6°explicit

explicit关键字阻止经过转换构造函数进行的隐式转换的发生

class A
{
public:
    explicit A(int a)
    {
        cout << "A(int a)" << endl;
    }

    explicit A(int a1, int a2)
    {
        cout << "A(int a1, int a2)" << endl;
    }

    A(const A & a)
    {
        cout << "A(const A& a)" << endl;
    }

private:
    int _a1;
    int _a2;
};

int main()
{
    A a1(1);
    //隐式转换-> A tmp(1); A a2(tmp);
    //再优化成直接构造
    //A a2 = 1;
    //多个参数也可以防止隐式类型转换
    //A a3 = { 1,2 };//C++11
}

【C++】22.单例模式+类型转换 完

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小恒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值