求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
C++中实现Singleton的正确方法
 
作者 至简李云,火龙果软件    发布于 2013-12-11
 

如果某个类管理了系统中唯一的某种资源,那么我们只能创建该类的一个实例,此时用到singleton设计模式(后面为了简化将省略“设计模式”四个字)就比较合适了。然而,如果不注意实现方法,就很有可能会让我们碰到一些莫名其妙的错误。图1是经过简化所得到的一个实现错误的例子。

main.c
00001: #include 
00002:
00003: using namespace std;
00004:
00005: class singleton1_t
00006: {
00007: public:
00008:     static singleton1_t *instance ()
00009:     {
00010:         return &instance_;
00011:     }
00012:
00013:     void count_increase () {count_ ++;}
00014:     int count () const {return count_;}
00015:
00016: private:
00017:     singleton1_t (): count_ (0) {}
00018:     ~singleton1_t () {}
00019:
00020:     static singleton1_t instance_;
00021:     int count_;
00022: };
00023:
00024: class singleton2_t
00025: {
00026: public:
00027:     static singleton2_t *instance ()
00028:     {
00029:         return &instance_;
00030:     }
00031:
00032: private:
00033:     singleton2_t () {singleton1_t::instance ()->count_increase ();}
00034:     ~singleton2_t () {}
00035:
00036:     static singleton2_t instance_;
00037: };
00038:
00039: singleton2_t singleton2_t::instance_;
00040: singleton1_t singleton1_t::instance_;
00041:
00042: int main ()
00043: {
00044:     (void) singleton2_t::instance ();
00045:     cout << "count = " << singleton1_t::instance ()->count () << endl;
00046:     return 0;
00047: }

图中的两个类在实现singleton时都将类的构造和析构函数的对外可视性设为private,这是实现singleton首先要注意的一个点。通过这一手段,有助于预防他人粗心地定义类实例。

图中的singleton2_t类在其构造函数中调用singleton1_t类的count_increase ()方法使计数加一。第44行的代码用于代表使用singleton2_t实例。第

<span style="font-family:'Courier New';">$ <strong>g++ main.cpp -o singleton.exe</strong>
$ <strong>./singleton.exe</strong>
count = 0
</span>

46行代码则显示singleton1_t类的记数信息。图 2示例了该程序的运行结果。

是不是对于最终的显示计数为0而不是1感到奇怪?错误发生的原因在于,singleton2_t类实例的构造是先于singleton1_t类的,当singleton1_t类的实例在最后构造时会把count_变量置成0,从而覆盖singleton2_t的构造函数所引起的变更。

尽管这是一个精心设计的错误,但在大型项目中出现这类错误的可能性却并不小。因为在现实项目中,singleton1_t和singleton2_t两个类的实现很可能是在不同的源文件中,这势必造成两个类实例的初始化顺序会因链接顺序不同而不同,《揭示C++中全局类变量的构造与析构顺序》一文介绍了这是为什么。

在本例中,如果将第39行和第40行的代码进行对调就不会出现这种奇怪的现象,但这不是解决问题的终极方法。更好的方法需要更改singleton的实现方法,图 3示例了一种新的实现方法。

<span style="font-family:'Courier New'"><strong><u>main.c</u></strong>
00001: #include <iostream>
00002:
00003: using namespace std;
00004:
00005: class singleton1_t
00006: {
00007: public:
00008: static singleton1_t *instance ()
00009: {
<u>00010</u>: if (0 == p_instance_) {
<u>00011</u>: p_instance_ = new singleton1_t;
<u>00012</u>: }
<u>00013</u>: return p_instance_;
00014: }
00015:
00016: void count_increase () {count_ ++;}
00017: int count () const {return count_;}
00018:
00019: private:
00020: singleton1_t (): count_ (0) {}
00021: ~singleton1_t () {}
00022:
<u>00023</u>: static singleton1_t *p_instance_;
00024: int count_;
00025: };
00026:
00027: class singleton2_t
00028: {
00029: public:
00030: static singleton2_t *instance ()
00031: {
<u>00032</u>: if (0 == p_instance_) {
<u>00033</u>: p_instance_ = new singleton2_t;
<u>00034</u>: }
<u>00035</u>: return p_instance_;
00036: }
00037:
00038: private:
00039: singleton2_t () {singleton1_t::instance ()->count_increase ();}
00040: ~singleton2_t () {}
00041:
<u>00042</u>: static singleton2_t *p_instance_;
00043: };
00044:
<u>00045</u>: singleton2_t *singleton2_t::p_instance_ = 0;
<u>00046</u>: singleton1_t *singleton1_t::p_instance_ = 0;
00047:
00048: int main ()
00049: {
00050: singleton2_t::instance ();
00051: cout << "count = " << singleton1_t::instance ()->count () << endl;
00052: return 0;
00053: }
</span>

新实现最大的变化,在于将以前的类静态变量从类实例变成了类指针,并在instance()函数中需要时通过new操作符创建类实例。指针在C++中仍是当作一种原始数据类型处理的,其初始化与类实例的初始化不同,不需调用类构造函数。在这一实现中,两个类的静态变量p_instance_的初始化都是在程序的.bss段初始化时一次性完成的。

这一实现中由于类的实例是通过new操作符获得的,所以需要为类定义释放实例的函数(图中省略了),并由在合适的时机调用。为了省去这类麻烦,作者更推崇图 4所示的实现方式。

<span style="font-family:'Courier New'"><strong>main.c</strong>
00005: class singleton1_t
00006: {
00007: public:
00008: static singleton1_t *instance ()
00009: {
00010: if (0 == p_instance_) {
<u>00011</u>: static singleton1_t instance;
<u>00012</u>: p_instance_ = &instance;
00013: }
00014: return p_instance_;
00015: }
00016:
00017: void count_increase () {count_ ++;}
00018: int count () const {return count_;}
00019:
00020: private:
00021: singleton1_t (): count_ (0) {}
00022: ~singleton1_t () {}
00023:
00024: static singleton1_t *p_instance_;
00025: int count_;
00026: };
</span>

通过在函数内部定义静态变量的方法获得类实例,一方面简化了类接口的实现,另一方面又降低了因为忘记调用释放接口函数而导致内存泄漏的可能。需要提醒的是,在这种实现方法中,类实例的构造是发生在各类的instance()函数第一次被调用时,而各实例的析构又是以与构造相反的顺序进行的,且后者是由编程语言环境所保证的。

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 
   
Visual C++编程命名规则
任何时候都适用的20个C++技巧
C语言进阶
串口驱动分析
轻轻松松从C一路走到C++
C++编程思想
更多...   


C++并发处理+单元测试
C++程序开发
C++高级编程
C/C++开发
C++设计模式
C/C++单元测试


北京 嵌入式C高质量编程
中国航空 嵌入式C高质量编程
华为 C++高级编程
北京 C++高级编程
丹佛斯 C++高级编程
北大方正 C语言单元测试
罗克韦尔 C++单元测试
更多...