SmartPtr
本博客已搬至:http://www.cnblogs.com/baiyanhuang/
posts - 29,comments - 176,trackbacks - 0

By SmartPtr(http://www.cppblog.com/SmartPtr/)

 

对于单件模式, 我想大家应该都不陌生,它可以说是GOF23个设计模式中最简单,最常用的一个模式了。但看似简单的东西却不一定好用,我就在使用时遇到了一个问题。

1 一个简单的Singleton

#include<iostream>
class Singleton
{
public:
    
static  Singleton& GetSingleton()
    {
        
static Singleton singleton;
        
return singleton;
    }
    
void Print()
    {
        std::cout
<<"Singleton Print\n";
    }
private:
    Singleton::Singleton()
    {
        std::cout
<<"singleton constructor\n";
    }
};

这个类提供了一些的功能(函数Print), 并禁止我们创建这个类的对象(构造函数为private), 提供给我们一个静态函数接口来访问这个单件对象(GetSingleton),利用静态变量的特点实现了其单一性。但是, 这个类有问题吗?
2 问题所在
是的,上面这个简单的类的确存在问题,而且是一个很严重的问题,这个问题让Singleton类完全失去它存在的意义, 因为它不再唯一!
是的, 当我们只在一个模块中使用这个类时(比如说,一个exe),这个类是没有问题的。但是, 一个稍微复杂一点的软件, 为了开发的便捷,提高复用度,降低耦合性等原因,其难免会被分成好几个模块。那么假设讲我现在有两个模块,一个DLL(singleton.dll), 用来提供一些基础的功能, 一个EXE(test.exe),用来提供真正的软件逻辑。 我现在singleton.dll中封装了一个Print的函数间(用类Singleton实现)并暴露出来。

singleton.dll

void Print()
{
    Singleton::GetSingleton().Print();
}

并在test.exe中这样调用:
Test.exe

Singleton::GetSingleton().Print();
Print();

这个时候,我们会发现在调用Singleton::GetSingleton().Print()时会产生一个Singleton对象, 而在调用Print()时, 也会产生一个Singleton对象, 也就是说我们有了两个Singleton实例, singleton不再是singleton。那么,为什么会这样呢。
static  Singleton& GetSingleton()
{
    static Singleton singleton;
    return singleton;
}

这个函数应该只会在第一次调用时创建Singleton对象,无论如何, 不应该出现会创建两次, 调用两次构造函数的情况。对于静态变量特性理解没错(只在第一次经过时被初始化), 编译器也没问题(vc8.0),难道两次经过该静态变量是都是第一次? 那么,难道两次调用的GetSingleton函数并不是同一个函数?让我们逐一来看:

1) Singleton::GetSingleton().Print()
在Test.exe中直接调用该函数,因为包含的头文件singleton.h有完整的实现, 在链接时会在Test.exe保存一份Singleton::GetSingleton()的实现代码。
将其标为Singleton::GetSingleton_1();
2) Print();
Print()函数是从singleton.dll中导出而来的,而Print()会调用Singleton::GetSingleton(), 在链接模块singleton.dll时,因为其包含的头文件有完整的实现, 这个DLL也会保存一份Singleton::GetSingleton()的执行代码。 我将它标为Singleton::GetSingleton_2(), 虽然我们包含的是同一个头文件,两个是相同的函数名字, 但是这个函数在两个不同的模块中都存有一份独立的实现。实际上, 他们已经成为两个不同的函数了。

看来,两个函数的确不是同一个函数。

3 如何解决

既然知道了原因,就会有相应的解决方法。既然我们知道有两份独立的代码分别存在于两个模块中, 那么我们要做的就是让它只有一份。最好的结果就是这个函数保存在dll中, 在Test.exe不再存有该函数的执行代码, 而是调用dll中的那个函数。现在结果很明显了:将Singleton.h编译链接singleton.dll并将外部需要使用的函数暴露出来。这样,不管有多少模块使用到singleton, 我们始终执行singleton.dll中的代码。

如下:
SINGLETON_API static Singleton& GetSingleton()
{
     static Singleton singleton;
     return singleton;
}
注:
#ifdef SINGLETON_EXPORTS
#define SINGLETON_API __declspec(dllexport)
#else
#define SINGLETON_API __declspec(dllimport)
#endif
这样在test.exe中使用该函数时,就不会再产生一个副本了,从而保证了我们的应用程序只有一个singleton实例

posted on 2007-08-28 11:37 SmartPtr 阅读(1497) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理