C++ 单例模式 模板类的小问题

C++ 单例模式 模板类的小问题

介绍:添加一个C++单例模式的小知识。

单例模式的知识

  1. 单例模式实现分为饿汉式和懒汉式。
  2. 单例模式需要实现线程安全。
  3. 单例模式所拥有的对象需要在程序退出时销毁。
  4. 单例模式需要私有化构造函数、借助静态成员实现。
  5. 本文重点:对于单例模板的使用一定要显示具体化声明,不然会导致生成多个模板实例,生成多个单例对象。

饿汉式

饿汉式天生是线程安全的,所以不需要借助 call_once 现线程安全。通过智能指针来自动调用析构函数。

cpp 复制代码
/*
 * 单例饿汉模式: 天生就是线程安全的, 内存分配在heap,返回的是指针
 * 但是不能保证对T本身操作是线程安全的!
 * 如果需要保证T的操纵也是线程安全的需要T自己实现线程安全
 * */
template<typename T>
class singleton_hungry_heap {
public:
    static T* get_ptr(){
        return singleton_hungry_heap::instance_.get();
    }

    static T& get_reference(){
        return *(singleton_hungry_heap::instance_.get());
    }
private:
    singleton_hungry_heap() = default;
    singleton_hungry_heap(const singleton_hungry_heap&) = default;
    singleton_hungry_heap& operator=(const singleton_hungry_heap&) = default;
    ~singleton_hungry_heap()= default;
private:
    static std::unique_ptr<T> instance_ ;
};
template<typename T>
std::unique_ptr<T> singleton_hungry_heap<T>::instance_ = std::unique_ptr<T>(new T());

懒汉式

需要借助 call_once 现线程安全。

cpp 复制代码
/*
 * 单例懒汉模式: 实现线程安全、利用pthread_once实现
 * 但是不能保证对T本身操作是线程安全的!
 * 如果需要保证T的操纵也是线程安全的需要T自己实现线程安全
 * */
template<typename T>
class singleton_lazy_heap{
private:
    singleton_lazy_heap() = default;
    singleton_lazy_heap(const singleton_lazy_heap&) = default;
    singleton_lazy_heap& operator=(const singleton_lazy_heap&) = default;
    ~singleton_lazy_heap()= default;
public:
    static T* get_ptr(){
        std::call_once(singleton_lazy_heap<T>::_flag, singleton_lazy_heap<T>::init);
        return instance_.get();
    }

    static T& get_reference(){
        std::call_once(singleton_lazy_heap<T>::_flag, singleton_lazy_heap<T>::init);
        return *(instance_.get());
    }
private:
    static void init(){
        singleton_lazy_heap::instance_ = std::unique_ptr<T>(new T());
    }

    static std::once_flag _flag;

    static  std::unique_ptr<T> instance_;
};

template<typename T>
std::once_flag singleton_lazy_heap<T>::_flag = std::once_flag();

template<typename T>
std::unique_ptr<T> singleton_lazy_heap<T>::instance_ = nullptr;

为什么要显示实例化

首先建一个 Person类,然后创建一个单例类 singleton_lazy_heap<Person>,为了方便声明定义都放在person.hpp中。

cpp 复制代码
struct Person{
private:
    int age{0};
public:
    explicit Person(): age(0){
        fmt::print("Person create!\n");
    }

    explicit Person(const int& _age): age(_age){}

    [[nodiscard]] int get_age() const{
        return age;
    }

    void set_age(const int& _age){
        this->age = _age;
    }

    ~Person(){
        fmt::print("Person destructor!\n");
    }
};

接下来我们定义另一个类 King,声明在 king.hpp,定义在king.cpp中。

cpp 复制代码
#include "singleton.hpp"
#include "person.hpp"

struct King {
public:
    int get_person_age();
};

定义内容:

cpp 复制代码
#include "king.hpp"

int King::get_person_age() {
   //返回Person单例的age
    return singleton_lazy_heap<Person>::get_ptr()->get_age();
}

然后我们在main.cpp中使用 King.hpp

cpp 复制代码
#include "singleton.hpp"
#include "person.hpp"
#include "king.hpp"

int main() {
    singleton_lazy_heap<Person>::get_ptr()->set_age(10);
    //内存中应该只有一个Person对象 age的值是10
    King k;
    //实际返回时 0
    std::cout << k.get_person_age() << std::endl; //0
}

你以为会返回10,结果返回的是0。然后调试查看返回的内存地址,你会发现结果生成了两个Person对象。

原因 :不同编译单元cpp文件中的 singleton_lazy_heap<Person>::get_ptr() 返回不同的指针,不同的编译单元中多次实例化该模板,导致每个实例都会有自己独立的状态,这样单例模式不就失效了吗?仅仅在一个cpp文件中是多线程安全进一个对象?这那叫什么单例模式!

解决方法 :不要使用隐式实例化 ,使用显示实例化。我们在Person文件中添加一行即可。

cpp 复制代码
struct Person{
private:
    int age{0};
public:
    explicit Person(): age(0){
        fmt::print("Person create!\n");
    }

    explicit Person(const int& _age): age(_age){}

    [[nodiscard]] int get_age() const{
        return age;
    }

    void set_age(const int& _age){
        this->age = _age;
    }

    ~Person(){
        fmt::print("Person destructor!\n");
    }
};

template class singleton_lazy_heap<Person>;

然后就正常了,返回10;

总结:单例模板类,一定要使用具体实例化,不然,跨文件使用单例对象时,不同的编译单元中会多次实例化模板,导致每个实例都会有自己独立的状态生成多个所谓的单例。

相关推荐
西北大程序猿13 分钟前
服务器代码知识点补充
服务器·开发语言·网络·c++·网络协议
yxc_inspire2 小时前
基于Qt的app开发第十四天
前端·c++·qt·app·面向对象·qss
Cai junhao3 小时前
【Qt】工具介绍和信号与槽机制
开发语言·c++·qt·qt6.3
byte轻骑兵12 小时前
【C++特殊工具与技术】优化内存分配(四):定位new表达式、类特定的new、delete表达式
开发语言·c++
广州正荣12 小时前
成绩管理革新者:C++驱动的智能数据处理平台
c++·人工智能·科技
90wunch13 小时前
对象回调初步研究
c++·windows·安全
Se_ren_di_pity13 小时前
C++ STL容器汇总
开发语言·c++
Wendy_robot13 小时前
【零基础勇闯嵌入式岗】从单片机低功耗中获得的启发
c++·单片机·嵌入式硬件
lul~16 小时前
[科研理论]无人机底层控制算法PID、LQR、MPC解析
c++·人工智能·无人机
我命由我1234517 小时前
STM32 开发 - 中断案例(中断概述、STM32 的中断、NVIC 嵌套向量中断控制器、外部中断配置寄存器组、EXTI 外部中断控制器、实例实操)
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·嵌入式