调库的时候单例出了问题

起因

发现这个问题是在我自己开发的软件 “北漂” ,时候遇到的。当时有有一个天气预报的模块,我为了练习动态库,把那个模块写成了动态库。发布程序时天气功能总是不好用。在网上搜解决方案时发现了这个问题,但是我自己程序处的问题其实不是因为这个,但我觉得着属于意外收获就记了下来。但是我windows上QT5.12.4这写问题没有复现。有可能新的编译器自己弥补了这个缺陷。但是以后工作的环境要是编译器版本老还有可能遇到。

示例

一个简单的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),利用静态变量的特点实现了其单一性。但是, 这个类有问题吗?

问题所在

是的,上面这个简单的类的确存在问题,而且是一个很严重的问题,这个问题让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(), 虽然我们包含的是同一个头文件,两个是相同的函数名字, 但是这个函数在两个不同的模块中都存有一份独立的实现。实际上, 他们已经成为两个不同的函数了。

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

解决方式

既然知道了原因,就会有相应的解决方法。既然我们知道有两份独立的代码分别存在于两个模块中, 那么我们要做的就是让它只有一份。最好的结果就是这个函数保存在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

参考

Windows解决方案
https://www.cnblogs.com/mokliu/archive/2011/07/18/2138792.html
linux解决方案
https://www.cnblogs.com/bourneli/archive/2011/12/28/2305280.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值