C++的模板可以帮助我们编写适合不同类型的模板类,给代码的复用性提供了极大的方便。近来写了一个涉及单例的C++模板类,简化下来可以归结为以下的代码:
| 1 2 3 4 5 6 7 8 9 10 11 | template<typenameT>classSingleton{public:    // 此处省去了多线程安全锁    staticT* getInstance()    {    staticT t;    return&t;  }}; | 
那么如果希望对某个work horse类,比如叫做Foo,定义一个Singleton,就会很容易啦:
| 1 | Foo* foo = Singleton<Foo>::getInstance(); | 
注意这里不需要自己释放foo,因为它不是new出来的。
如果代码被编译成“一个”so(dll)或可执行文件,这里的Singleton得到的对象却是是单例的,也就是说,某一种类型得到的对象地址是确定的。
但是,如果同一个类型的单例在不同的so(dll,可执行文件)中使用,那么得到的同一个类型的单例对象,其地址也是不一样的。比如
libfoo.so文件中的如下代码:
| 1 2 | Foo* foo = Singleton<Foo>::getInstance();std::type_info fooType = typeid(Singleton<Foo>); | 
和libbar.so中的另一端代码:
| 1 2 3 | Foo* bar = Singleton<Foo>::getInstance();std::type_info barType = typeid(Singleton<Foo>); | 
其中foo和bar的地址是不同的!他们并不是真正的单例。
为什么呢?原因是模板是编译的时候实例化成“真正的类”的,而在两个不同的so(dll)编译生成的过程中,编译器进行了两个不同的实例化过程,他们被实例化成了不同的类。也不是完全不同,但有部分是不同的。类的模板具现化实在“编译时期而非执行时期” 《C++对象模型》 指出 ,你在编译dll的时候,虽然链入的是同一个模板,但是实际上dll中和exe中都存在了一份相同的代码了
比如,对上述两个so,如果去测试两个Singleton类型是否为同一类型(RTTI):
| 1 | if(fooType == barType) | 
那么该测试会返回false。但是,如果测试两个类型的名字是否相等:
| 1 | if(strcmp(fooType.name(), barType.name()) == 0) | 
该测试则会返回true。
这说明,两个类型虽然type_info不同,但名字却是相同的。
想要真正的跨dll使用模板类单例,必须在生成dll时用XXX_EXPORT进行导出,在其他dll使用单例时用XXX_IMPORT进行导入,这样就可以真正的跨dll使用模板类单例了。