在我自己写的一个工厂类实现中,每个产品会注册创建接口到这个工厂类。工厂类使用这些
注册进来的创建接口来完成产品的创建。其结构大致如下:
product *factory::create( long product_type )
{
creator c = m_creators[product_type];
return c();
}
factory::instance().register( PRODUCT_A_TYPE, productA::create );
...
factory::instance().create( PRODUCT_A_TYPE );
这个很普通的工厂实现中,需要写上很多注册代码。每次添加新的产品种类时,也需要修改
这些的注册代码。而恰好,这些注册代码可能会被放在一个统一的地方。为了消除这个地方
,我使用了偶然间看到的<Modern C++ design>里的做法:
const bool _local = factory::instance().register( PRODUCT_A_TYPE,...
也就是说,通过对全局常量_local的自动初始化,来自动完成对该产品的注册。
结果,因为这些代码全部被放置于一个静态库。最终的代码文件结构大致为:
lib
- product_a.cpp : 定义了全局常量_local
- product_a.h
- factory.cpp
- factory.h
exe
- main.cpp
现在看起来世界很美,因为factory甚至不知道世界上还有个跟上层逻辑相关的product_a。
这种模块耦合几乎为0的结构让我窃喜。
悲剧的事情首先发生于,开VC调试器,发现打在product_a.cpp里的断点失效。就是那个总
是提示说没有为该文件加载调试符号。开始还不在意,以为又是代码和调试符号文件不匹配
的原因,折腾了好久,不得其果。
后来分析了下,发现这个调试提示,就像我开着调试器打开了一个非本工程的代码文件,而
断点就打在这个文件里一样。也就是说,VC把我product_a.cpp当成不是这个工程里的代码
文件。
按照这个思路写些实验代码,最终发现问题所在:VC链接器根本没链接进product_a.cpp里
的代码。表现出来的情况就是,该编译单元里的全局常量(全局变量一样)根本没有得到初
始化,因为我跟到factory::register并没有被调用到。为什么VC不链接这个编译单元对应
的目标文件?或者说,为什么VC不初始化这个全局常量?
原因就在于,product_a.cpp太独立了。一个在整个编译链接阶段都无法确定该文件是否被
使用的文件,VC就直接不链接了。相反,当在factory.cpp里写下类似代码:
void test()
{
product_a obj;
}
虽然说test函数不会被调用,一切情况也变得正常了。好了,不扯了,给最后我的结论:
1、如果静态库中某个编译单元在编译阶段被确认为它并没有被外部使用,那么当这个静态
库被链接进可执行文件时,链接器忽略掉该编译单元里的代码,那么,链接器自然也不会为
该编译单元里出现的全局变量常量生成初始化代码(关于这部分初始化代码可以阅读
<linker and loader>一书);
2、上面那条结论存在一种传染性,意思是,当可执行文件里的代码使用到静态库中文件A里
的代码,A里又有地方使用到B里的代码,那么B依然会被链接。这种依赖性,应该可以让编
译器在编译阶段就发现(显然,上面我举的例子里,factory只有在运行期间才会依赖到
product_a.cpp里的代码)