C++单例模式思考

C++单例模式思考

1. 简单实现

单例顾名思义,就是全局唯一,有且只有一个对象,不允许同时存在多个对象,所以通常单例在C++里面有固有的实现格式——将构造函数声明成private或者protected,目的是不让外部使用者通过构造函数来创建对象。对外暴露一个静态函数获取单例对象,对内维护全局唯一的静态对象实例。具体实现如下所示:

class Singleton {
protected:
    Singleton(){ }

private:
    static void SingletonInit() {
        single_object_ = new Singletone();
    }

public:
    virtual ~Singleton(){ }
    static Singleton* GetInstance() {
        if (NULL == single_object_) {
            pthread_once(&once_, &SingletonInit);
        }

        return single_object_;
    }

private:
    static Singleton* single_object_;
    static pthread_once_t once_;
};

这里用到了pthread_once调用,目的是保证单例的创建在多线程环境下是线程安全的。对外仅仅暴露GetInstance方法来获取单例类的对象。当然这是最简单的单例的实现,那么问题来了,如何将单例的实现逻辑抽象出来作为单例基类?如何保证单例对象在对象周期结束之后能够被正确释放,而不会造成内存泄露?

2.智能指针加模板实现单例基类

为了抽象出单例基类,利用模板,将单例逻辑抽象出来,内部维护的单例对象为T*类型。与此同时,为了让单例维护的内存空间能够被正确释放,这里采用智能指针代替裸指针,具体实现如下代码所示:

#include <pthread.h>
#include <tr1/memory>

template<class T>
class Singleton {
protected:
    Singleton(){ }

private:
    static void SingletonInit() {
        single_object_.reset( new T );
    }

public:
    virtual ~Singleton(){ }
    static std::tr1::shared_ptr<T> GetInstance() {
        if(NULL == single_object_.get()) {
            pthread_once(&once_, &SingletonInit); 
        }

        return single_object_;
    }

private:
    static std::tr1::shared_ptr<T> single_object_;
    static pthread_once_t once_;
};

//using pthread_once() the keep multi thread safe
template<class T>
pthread_once_t Singleton<T>::once_ = PTHREAD_ONCE_INIT;

template<class T>
std::tr1::shared_ptr<T> Singleton<T>::single_object_;

有几点需要注意,由于采用了智能指针,在单例基类中,其析构函数必须声明成public(这是因为智能指针以对象的方式管理资源,在智能指针内部会调用所维护对象的析构函数来释放资源)。那么问题是如何使用单例基类呢?在这里,我结合工厂模式和单例模式,利用单例基类实现一个单例工厂,具体工厂类声明代码如下:
‘ObjectFactory.h’

#ifndef OBJECTFACTORY_
#define OBJECTFACTORY_
#include "Object.h"
#include "Singleton.h"

#include <tr1/memory>
#include <map>

using namespace std;

class Object;
class ObjectFactory:public Singleton<ObjectFactory> {
    friend class Singleton<ObjectFactory>;

private:
    ObjectFactory() { }

public:
    ~ObjectFactory(){ 
    }

public:
    //this is also thread unsafe;
    tr1::shared_ptr<Object> GetObject( string name ) {
        if( factory_map_.count(name) == 0 ) {
            factory_map_[name].reset(new Object(name));
        }

        return factory_map_[name];
    }

private:
    map<string , tr1::shared_ptr<Object> > factory_map_;
};

#endif

在单例工厂中,单例工厂继承单例基类,并且需要声明单例基类为工厂类的友元类,这是因为单例基类会显示调用单例工厂的构造函数来创建对象,但是为了保证单例工厂构造函数不至于被外部显示调用,单例工厂构造函数必须声明为private或者protected,所以这里需要声明友元类,单例基类才可以调用单例工厂类的构造函数来构造对象。

这段代码存在一个问题,在工厂类中生产对象的时候也会存在线程安全问题,所以这里在单例工厂类生产对象的时候进行加锁,避免重复生产对象造成内存的泄露。

<think>嗯,用户想了解如何在C++单例模式中正确使用std::map。首先,我需要回顾单例模式的基本概念,确保自己理解正确。单例模式确保一个类只有一个实例,并提供一个全局访问点。通常通过将构造函数私有化,并提供静态方法获取实例来实现。 接下来,用户提到要使用std::map,所以需要将std::map作为单例类的成员。我需要考虑如何在单例类中正确初始化和管理这个map。可能需要注意线程安全的问题,尤其是在多线程环境下,多个线程可能同时访问或修改map,导致数据竞争。 然后,参考用户提供的引用内容,引用[1]提到了使用模板函数或者std::call_once来实现单例。这两种方法都可以用来确保单例的线程安全初始化。引用[4]提到静态函数的优点,比如全局访问,这可能与单例模式的需求相关。 用户可能希望看到一个具体的示例代码,展示如何在单例类中嵌入std::map,并确保线程安全。同时,注意事项部分需要涵盖线程安全、内存管理、单例的析构问题以及map的访问控制。 考虑到C++11之后的特性,比如std::call_once和静态局部变量的线程安全初始化,这些都可以用来优化单例的实现。另外,使用互斥锁(如std::mutex)来保护对map的并发访问是必要的,避免数据竞争。 还需要注意单例的析构问题,如果单例持有资源,可能需要确保在程序结束时正确释放。但有时候,如果资源如std::map会在程序结束时自动释放,可能不需要显式处理,但用户应该了解潜在的问题。 可能的陷阱包括:未正确实现线程安全导致竞态条件,map的访问未加锁导致数据不一致,单例的拷贝或赋值未禁用导致多个实例被创建。 现在需要将这些思考整理成一个结构化的回答,包含示例代码和注意事项,并引用用户提供的参考资料中的相关内容,如引用[1]中的std::call_once或静态函数方法,引用[4]中提到的静态函数的优点等。 最后,生成相关问题,帮助用户深入理解单例模式的其他应用场景、线程安全的不同实现方法,以及与资源管理相关的问题。</think>以下是如何在C++单例模式中正确使用`std::map`的详细说明: ### 单例模式中使用`std::map`的示例 #### 1. 基础实现(C++11线程安全版) ```cpp #include <map> #include <mutex> class SingletonWithMap { private: std::map<int, std::string> data_map_; static SingletonWithMap* instance_; static std::mutex mutex_; SingletonWithMap() = default; // 私有构造函数 SingletonWithMap(const SingletonWithMap&) = delete; SingletonWithMap& operator=(const SingletonWithMap&) = delete; public: static SingletonWithMap& getInstance() { std::lock_guard<std::mutex> lock(mutex_); if (!instance_) { instance_ = new SingletonWithMap(); } return *instance_; } void insertData(int key, const std::string& value) { std::lock_guard<std::mutex> lock(mutex_); data_map_[key] = value; } std::string getData(int key) { std::lock_guard<std::mutex> lock(mutex_); return data_map_.count(key) ? data_map_[key] : ""; } }; // 静态成员初始化 SingletonWithMap* SingletonWithMap::instance_ = nullptr; std::mutex SingletonWithMap::mutex_; ``` #### 2. 改进版(使用`std::call_once`) ```cpp #include <map> #include <mutex> #include <memory> #include <atomic> class OptimizedSingleton { private: std::map<std::string, int> config_map_; static std::once_flag init_flag_; static OptimizedSingleton* instance_; OptimizedSingleton() { // 初始化默认配置 config_map_ = {{"timeout", 30}, {"retry", 3}}; } public: static OptimizedSingleton& getInstance() { std::call_once(init_flag_, []() { instance_ = new OptimizedSingleton(); }); return *instance_; } int getConfig(const std::string& key) const { auto it = config_map_.find(key); return (it != config_map_.end()) ? it->second : -1; } }; // 静态成员初始化 std::once_flag OptimizedSingleton::init_flag_; OptimizedSingleton* OptimizedSingleton::instance_ = nullptr; ``` ### 关键注意事项 1. **线程安全设计** - 对`std::map`的读写操作必须通过互斥锁(如`std::mutex`)保护,避免数据竞争 - 单例初始化推荐使用`std::call_once`(如示例2),比双重检查锁定更可靠[^1] 2. **内存管理** - 如果使用`new`创建单例,需考虑程序退出时的资源释放 - 可使用智能指针改进: ```cpp static std::shared_ptr<Singleton> instance_; // 初始化时使用std::make_shared ``` 3. **Map操作优化** - 批量操作时减少锁粒度: ```cpp void batchInsert(const std::map<K,V>& data) { std::lock_guard<std::mutex> lock(mutex_); data_map_.insert(data.begin(), data.end()); } ``` - 考虑使用`unordered_map`替代`map`以提高查找效率(当不需要有序遍历时) 4. **单例模式限制** - 禁用拷贝构造和赋值运算符(如示例中的`= delete`) - 提供线程安全的延迟初始化(首次访问时创建) 5. **异常处理** - 在map操作中需要处理可能的异常(如内存分配失败): ```cpp try { data_map_.emplace(key, value); } catch (const std::bad_alloc& e) { // 处理内存不足的情况 } ``` ### 应用场景示例 - 全局配置管理 - 跨模块数据共享 - 对象工厂注册表 - 资源缓存池
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值