C++ Singleton (单例) 模式最优实现

C++ Singleton (单例模式最优实现

原文:http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/

索引

· 静态化并不是单例 (Singleton) 模式

· 饿汉模式

· 懒汉模式 (堆栈-粗糙版)

· 懒汉模式 (局部静态变量-最佳版)

· 范例代码和注意事项 (最优实现)

· 扩展阅读

· 参考资料

我非常赞成合理的使用 设计模式 能让代码更容易理解和维护不过我自己除了简单的 单例 (Singleton) 模式 外其它都很少用。

可耻的是直到前段时间拜读了 C++ In Theory: The Singleton Pattern, Part I我才发现自己的 单例 (Singleton) 模式 写法还有改进空间.

文章作者 J. Nakamura 以 Log 日志类列举了 单例 (Singleton) 模式 的三种写法:

// log.h

#ifndef __LOG_H

#define __LOG_H

#include <list>

#include <string>

class Log {

public:

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  std::list<std::string> m_data;

};

#endif // __LOG_H

静态化并不是单例 (Singleton) 模式

初学者可能会犯的错误误以为把所有的成员变量和成员方法都用 static 修饰后就是单例模式了:

class Log {

public:

  static void Write(char const *logline);

  static bool SaveTo(char const *filename);

private:

  static std::list<std::string> m_data;

};

In log.cpp we need to add

std::list<std::string> Log::m_data;

乍一看确实具备单例模式的很多条件不过它也有一些问题第一静态成员变量初始化顺序不依赖构造函数得看编译器心情的没法保证初始化顺序 (极端情况有 a b 两个成员对象b 需要把 a 作为初始化参数传入你的类就 必须 得要有构造函数并确保初始化顺序).

第二最严重的问题失去了面对对象的重要特性 -- "多态", 静态成员方法不可能是 virtual 的Log 类的子类没法享受 "多态带来的便利.

饿汉模式

饿汉模式 是指单例实例在程序运行时被立即执行初始化:

class Log {

public:

  static Log* Instance() {

    return &m_pInstance;

  }

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  Log();              // ctor is hidden

  Log(Log const&);    // copy ctor is hidden

  static Log m_pInstance;

  static std::list<std::string> m_data;

};

// in log.cpp we have to add

Log Log::m_pInstance;

这种模式的问题也很明显类现在是多态的但静态成员变量初始化顺序还是没保证.

还引起另外一个问题 (我之前碰到过的真实事件以后便一直采用下面提到的 "懒汉模式"): 有两个单例模式的类 ASingleton 和 BSingleton, 某天你想在 BSingleton 的构造函数中使用 ASingleton 实例这就出问题了因为 BSingleton m_pInstance 静态对象可能先 ASingleton 一步调用初始化构造函数结果 ASingleton::Instance() 返回的就是一个未初始化的内存区域程序还没跑就直接崩掉.

懒汉模式 (堆栈-粗糙版)

J. Nakamura 把它叫作 "Gamma Singleton", 因为这是 Gamma 在他大名鼎鼎的 <<设计模式>> (<<Design Patterns>>) [Gamma] 一书采用的方法称它为 "懒汉模式是因为单例实例只在第一次被使用时进行初始化:

class Log {

public:

  static Log* Instance() {

    if (!m_pInstance)

      m_pInstance = new Log;

    return m_pInstance;

  }

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  Log();        // ctor is hidden

  Log(Log const&);    // copy ctor is hidden

  static Log* m_pInstance;

  static std::list<std::string> m_data;

};

// in log.cpp we have to add

Log* Log::m_pInstance = NULL;

Instance() 只在第一次被调用时为 m_pInstance 分配内存并初始化看上去所有的问题都解决了初始化顺序有保证多态也没问题.

不过细心的你可能已经发现了一个问题程序退出时析构函数没被执行这在某些设计不可靠的系统上会导致资源泄漏比如文件句柄, socket 连接内存等等幸好 Linux / Windows 2000/XP 等常用系统都能在程序退出时自动释放占用的系统资源不过这仍然可能是个隐患至少 J. Nakamura 印象中有些系统是不会自动释放的.

对于这个问题比较土的解决方法是给每个 Singleton 类添加一个 destructor() 方法:

virtual bool destructor() {

    // ... release resource

    if (NULL!= m_pInstance) {

        delete m_pInstance;

        m_pInstance = NULL;

    }

}

然后在程序退出时确保调用了每个 Singleton 类的 destructor() 方法这么做虽然可靠但却很是繁琐幸运的是, Meyers 大师有个更简便的方法.

懒汉模式 (局部静态变量-最佳版)

它也被称为 Meyers Singleton [Meyers]:

class Log {

public:

  static Log& Instance() {

    static Log theLog;

    return theLog;

  }

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  Log();          // ctor is hidden

  Log(Log const&);      // copy ctor is hidden

  Log& operator=(Log const&);  // assign op is hidden

  static std::list<std::string> m_data;

};

在 Instance() 函数内定义局部静态变量的好处是theLog `` 的构造函数只会在第一次调用 ``Instance() 时被初始化达到了和 "堆栈版相同的动态初始化效果保证了成员变量和 Singleton 本身的初始化顺序.

它还有一个潜在的安全措施Instance() 返回的是对局部静态变量的引用如果返回的是指针,Instance() 的调用者很可能会误认为他要检查指针的有效性并负责销毁构造函数和拷贝构造函数也私有化了这样类的使用者不能自行实例化.

另外多个不同的 Singleton 实例的析构顺序与构造顺序相反.

范例代码和注意事项 (最优实现)

把下面 C++ 代码片段中的 Singleton 替换成实际类名快速得到一个单例类:

class Singleton {

public:

    static Singleton& Instance() {

        static Singleton theSingleton;

        return theSingleton;

    }

    /* more (non-static) functions here */

private:

    Singleton();                            // ctor hidden

    Singleton(Singleton const&);            // copy ctor hidden

    Singleton& operator=(Singleton const&); // assign op. hidden

    ~Singleton();                           // dtor hidden

};

Note

任意两个 Singleton 类的构造函数不能相互引用对方的实例否则会导致程序崩溃:

ASingleton& ASingleton::Instance() {

    const BSingleton& b = BSingleton::Instance();

    static ASingleton theSingleton;

    return theSingleton;

}

BSingleton& BSingleton::Instance() {

    const ASingleton & b = ASingleton::Instance();

    static BSingleton theSingleton;

    return theSingleton;

}

在多线程的应用场合下必须小心使用如果唯一实例尚未创建时有两个线程同时调用创建方法且它们均没有检测到唯一实例的存在便会同时各自创建一个实例这样就有两个实例被构造出来从而违反了单例模式中实例唯一的原则解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁 (虽然这样会降低效率).

多个 Singleton 实例相互引用的情况下需要谨慎处理析构函数初始化顺序为 ASingleton »BSingleton » CSingleton 的三个 Singleton 其中 ASingleton BSingleton 的析构函数调用了CSingleton 实例的成员函数程序退出时CSingleton 的析构函数 将首先被调用导致实例无效那么后续 ASingleton BSingleton 的析构都将失败导致程序异常退出.

扩展阅读

· 反模式 : 在实践中明显出现但又低效或是有待优化的设计模式是用来解决问题的带有共同性的不良方法它们已经经过研究并分类以防止日后重蹈覆辙并能在研发尚未投产的系统时辨认出来. (其中的一些反模式还挺有意思的);

· C++ In Theory: The Singleton Pattern, Part 2 : "C++ In Theory" 系列的第二部分主要内容是泛型编程中的单例模式我对泛型不太感冒感兴趣的朋友可以看看.

参考资料

[Gamma]

Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides.

[Meyers]

More Effective C++: S.Meyers.

font-sto+�Dnr(��H�-size:24.0000pt; font-family:'Open Sans'; background:rgb(255,255,255); mso-shading:rgb(255,255,255); " >

[Gamma]

Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides.

[Meyers]

More Effective C++: S.Meyers.

w�(-Etn(��H�nt-style:normal; font-size:12.5000pt; font-family:'Open Sans'; background:rgb(255,255,255); mso-shading:rgb(255,255,255); " > 一步调用初始化构造函数 结果 ASingleton::Instance() 返回的就是一个未初始化的内存区域 程序还没跑就直接崩掉 .

懒汉模式 (堆栈-粗糙版)

J. Nakamura 把它叫作 "Gamma Singleton", 因为这是 Gamma 在他大名鼎鼎的 <<设计模式>> (<<Design Patterns>>) [Gamma] 一书采用的方法称它为 "懒汉模式是因为单例实例只在第一次被使用时进行初始化:

class Log {

public:

  static Log* Instance() {

    if (!m_pInstance)

      m_pInstance = new Log;

    return m_pInstance;

  }

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  Log();        // ctor is hidden

  Log(Log const&);    // copy ctor is hidden

  static Log* m_pInstance;

  static std::list<std::string> m_data;

};

// in log.cpp we have to add

Log* Log::m_pInstance = NULL;

Instance() 只在第一次被调用时为 m_pInstance 分配内存并初始化看上去所有的问题都解决了初始化顺序有保证多态也没问题.

不过细心的你可能已经发现了一个问题程序退出时析构函数没被执行这在某些设计不可靠的系统上会导致资源泄漏比如文件句柄, socket 连接内存等等幸好 Linux / Windows 2000/XP 等常用系统都能在程序退出时自动释放占用的系统资源不过这仍然可能是个隐患至少 J. Nakamura 印象中有些系统是不会自动释放的.

对于这个问题比较土的解决方法是给每个 Singleton 类添加一个 destructor() 方法:

virtual bool destructor() {

    // ... release resource

    if (NULL!= m_pInstance) {

        delete m_pInstance;

        m_pInstance = NULL;

    }

}

然后在程序退出时确保调用了每个 Singleton 类的 destructor() 方法这么做虽然可靠但却很是繁琐幸运的是, Meyers 大师有个更简便的方法.

懒汉模式 (局部静态变量-最佳版)

它也被称为 Meyers Singleton [Meyers]:

class Log {

public:

  static Log& Instance() {

    static Log theLog;

    return theLog;

  }

  virtual void Write(char const *logline);

  virtual bool SaveTo(char const *filename);

private:

  Log();          // ctor is hidden

  Log(Log const&);      // copy ctor is hidden

  Log& operator=(Log const&);  // assign op is hidden

  static std::list<std::string> m_data;

};

在 Instance() 函数内定义局部静态变量的好处是theLog `` 的构造函数只会在第一次调用 ``Instance() 时被初始化达到了和 "堆栈版相同的动态初始化效果保证了成员变量和 Singleton 本身的初始化顺序.

它还有一个潜在的安全措施Instance() 返回的是对局部静态变量的引用如果返回的是指针,Instance() 的调用者很可能会误认为他要检查指针的有效性并负责销毁构造函数和拷贝构造函数也私有化了这样类的使用者不能自行实例化.

另外多个不同的 Singleton 实例的析构顺序与构造顺序相反.

范例代码和注意事项 (最优实现)

把下面 C++ 代码片段中的 Singleton 替换成实际类名快速得到一个单例类:

class Singleton {

public:

    static Singleton& Instance() {

        static Singleton theSingleton;

        return theSingleton;

    }

    /* more (non-static) functions here */

private:

    Singleton();                            // ctor hidden

    Singleton(Singleton const&);            // copy ctor hidden

    Singleton& operator=(Singleton const&); // assign op. hidden

    ~Singleton();                           // dtor hidden

};

Note

任意两个 Singleton 类的构造函数不能相互引用对方的实例否则会导致程序崩溃:

ASingleton& ASingleton::Instance() {

    const BSingleton& b = BSingleton::Instance();

    static ASingleton theSingleton;

    return theSingleton;

}

BSingleton& BSingleton::Instance() {

    const ASingleton & b = ASingleton::Instance();

    static BSingleton theSingleton;

    return theSingleton;

}

在多线程的应用场合下必须小心使用如果唯一实例尚未创建时有两个线程同时调用创建方法且它们均没有检测到唯一实例的存在便会同时各自创建一个实例这样就有两个实例被构造出来从而违反了单例模式中实例唯一的原则解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁 (虽然这样会降低效率).

多个 Singleton 实例相互引用的情况下需要谨慎处理析构函数初始化顺序为 ASingleton »BSingleton » CSingleton 的三个 Singleton 其中 ASingleton BSingleton 的析构函数调用了CSingleton 实例的成员函数程序退出时CSingleton 的析构函数 将首先被调用导致实例无效那么后续 ASingleton BSingleton 的析构都将失败导致程序异常退出.

扩展阅读

· 反模式 : 在实践中明显出现但又低效或是有待优化的设计模式是用来解决问题的带有共同性的不良方法它们已经经过研究并分类以防止日后重蹈覆辙并能在研发尚未投产的系统时辨认出来. (其中的一些反模式还挺有意思的);

· C++ In Theory: The Singleton Pattern, Part 2 : "C++ In Theory" 系列的第二部分主要内容是泛型编程中的单例模式我对泛型不太感冒感兴趣的朋友可以看看.

参考资料

[Gamma]

Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides.

[Meyers]

More Effective C++: S.Meyers.


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值