关于从DLL导出模板函数

模板在C++中本来就是个比较复杂的领域,而当它和DLL结合到一起时,事情变得更加有点复杂而有趣,最近就遇到了这样一个问题。 我有一个用于生成单件的类模板,它声明在一个DLL项目中,如下。 #if defined MY_EXPORTS #define EXPORT_API __declspec(dllexport) #else #define EXPORT_API __declspec(dllimport) #endif template < typename T > class Singleton { public: EXPORT_API static T& GetSingleton() { static T singleton; return singleton; } // …省去其它代码,比如隐藏构造函数,禁止复制构造和赋值函数.. }; 我试图导出GetSingleton()函数,因为我希望在其他导入该DLL的模块中能够通过调用这个函数而得到单例。基于该Singleton类模板定义一个单件的类很简单,只需要继承实例化的类即可,比如我在DLL内部定义了两个单件类,LogManager和ResourceManager,他们的定义看起来像这样。 class LogManager : public Singleton< LogManager > { // …… } class ResourceManager : public Singleton< ResourceManager > [...]

Posted in: C++ by Benny Chen No Comments

小心动态链接库链接静态库时的内存错误

最近写的模块,在独立的应用程序中测试是没问题的,但把它装配成DLL后,再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的: module就是我所写的模块,在这里被封装为DLL,因为要使用json相关的功能,该DLL链接了一个静态库 (jsoncpp.lib)。最后在应用程序中导入并使用module.dll,同时因为在应用程序中也需要用到json,所以应用程序也链接了jsoncpp.lib。 以下用一些伪代码来描述这些模块间的调用关系,以具现出这个错误。 jsoncpp.lib为c++提供了功能齐全的json操作,其核心的类是Json::Value。(阅读本篇文章你无需了解太多json) module.dll中导出了一个接口: //ModuleClass.h #include "json/value.h" #if defined MODULE_EXPORTS #define MODULE_EXPORTS __declspec(dllexport) #else #define MODULE_EXPORTS __declspec(dllimport) #endif class ModuleClass { public: MODULE_EXPORTS void AllocSomeMemory( Json::Value &root ) { // 这将申请一些内存,因为会new出一个Json::Value,并append到root上 root.append( "testString" ); } }; 应用程序: #include "json/value.h" #include "ModuleClass.h" int main() { Json::Value root; ModuleClass::AllocSomeMemory( root ); } 在Debug模式下,当main函数执行完毕,对Json::Value root进行析构时,程序便出现了异常。分析下,很显然,调用ModuleClass::MallocMemoryHere时申请的内存,是在module.dll中申请的,而对这些内存的析构则是在应用程序(.exe)中进行的(析构root会同时析构append在root上的所有子Json::Value)。不过,这是异常的真正原因么? 追踪到异常的出错点:dbgheap.c文件中那句ASSERT语句。 [...]

Posted in: C++ by Benny Chen No Comments , ,

Static Initialization Order Fiasco

Static Initialization Order Fiasco (SIOF),我也是最近才知道了这个说法,因为在开发程序的时候被它bug了:对于一个static变量,不管它是全局的或者是类的成员变量,访问它的时候不一定总是成功的,甚至会造成程序crash,因为不能保证它在被访问时已经被初始化了(跟初始化的顺序有关,所以称为初始化顺序的Fiasco)。以下将制造一个非常简单的SIOF情形: Whatever.h #include <vector> #include <string> class Whatever { public: Whatever() { cout << "Construct Whatever" << endl; Display(); } ~Whatever() { cout << "Destruct Whatever" << endl; Display(); } void Display() { cout << "static int:" << i << endl; cout << "static string:" << m_str << endl; cout << [...]

让管理lua_State的类指针安全

最近因为在公司的项目中接手了Lua脚本模块的开发,所以研究了很多关于Lua脚本的东西,秉着“多看多想多记”的原则,我时刻敦促自己要及时记录下遇到的一些问题和想法。 在Lua中,几乎所有的东西都是围绕着lua_State转,所以,一般我们都会写一个类来封装管理它,比如: class LuaObject { public: LuaObject() { m_L = luaL_newstate(); luaL_openlibs( m_L ); } ~LuaObject() { if ( m_L ) { lua_close( m_L ); m_L = NULL; } } private: lua_State *m_L; }; 这很好,不过它不是指针安全的。试想,如果一个LuaObject对象被复制,结果将会怎样。 LuaObject luaObject1; LuaObject luaObject2( luaObject1 ); 上面这段代码将会导致运行时crash,因为luaObject1和luaObject事实上指向了同一块lua_State,这样当luaObject1和luaObject2被析构时,lua_State会被两次lua_close,这不crash才怪呢! 事实是,当一个类包含了一个指针时,我们就需要开始变得格外谨慎,除了在构造函数和析构函数中要处理指针的初始化和清理外,我们还需要考虑深拷贝(deep copy),浅拷贝(shallow copy))的问题。如果使用编译器默认生成的拷贝构造函数,它只会浅拷贝指针,而指针所指向的内存区域不会被拷贝。就像上面一样,两个LuaObject实则共享了一个lua_State。 那该如何处理让管理LuaObject类的指针安全呢,深拷贝?厄,首先我也没有深究深度拷贝lua_State具体该如何完成,不过我猜想这可是一个复杂而重型的操作,仅仅为了带来指针安全而选择此可不是一个明智的选择。 如果你是开发LuaObject类的程序员,也许你会对使用LuaObject的程序员(或许有时使用者就是你自己)说:“你不要对它进行拷贝操作不就OK了”,但这是一种严重不负责任的行为,因为这种皮球会越踢越远的。比如,程序员A使用了你的LuaObject类,他写了一个包含LuaObject的指针的类,同样A也不考虑拷贝指针安全问题,然后A又将它的类传递给了B,B封装了A的类指针,然后传递给了C,接着如此重复,C再给D,D再给E,最后E在对类进行拷贝操作时,程序crash掉了,因为在某个最底层,他们共享了lua_State。不过因为这时候已经嵌套了这么多层,E程序员或许根本不知道也不关心什么是LuaObject(他只跟D的类打交道)。这时,要想追踪到BUG之源——万恶的不考虑指针安全的LuaObject,已经非常困难了,God bless you all. 事实是,如果你不想让你的类被复制,你就应该明确而显示的禁止它。其实lua_State的这个问题非常类似于Effective C++书中的第14个条款中所提到的问题:在资源管理类中小心copying行为。对于这个问题,书中提供了两种解决方案。第一种就是禁止复制。第二种则是对底层资源的“引用计数法”(reference count)。 第一种禁止复制很简单。(关于Uncopyable) class [...]

[译] Google的C++风格参考指南(一)──头文件

译者的话: 在一个开发团队中,很重要的一点是要遵循一个统一的风格,这样才能便于相互之间在代码上进行快速的交流和理解。反之,如果团队中的每个人只是按照自 己的个性去写属于个人风格的代码,这必然会造成项目代码的五花八门,互相之间难以沟通,整个项目的代码质量低下,BUG率攀高,软件的可维护率也极差。长 此以往,国将不国,团队将不团队,最终软件极有可能以一种极其混乱的状态收尾,这真是一个悲剧。 每一个C++程序员都有自己的编程风格,虽然大家的风格迥异,但也一定有一些共通的地方。我希望能找到一个对于C++风格的归纳和总结,这样在以后 我的工作中,对我自己,甚至对我工作的团队,当然也对于正在阅读的你,能够有一个帮助和参考。所以,我决定翻译Google的这篇C++风格参考指南。当 然,真正促使我去翻译这篇文章的原因是,我是一个C++技术和简洁代码风格的狂热追捧者。 注意,这只是一篇参考,它是Google的C++风格,你并不一定严格的遵循所有的规则,永远记住很重要的一点,风格永远跟着你的团队走。 本人能力有限,所以不一定每个地方都翻译的准确,另外一些不太好翻译的专业名词首次出现时,我会标注英文原文。 原文:Google C++ Style Guide 背景 (Background) C++是很多Google开源项目所使用的开发语言。正如每一个C++程序员所熟知的,C++有很多强大的功 能,但功能强大也同时意味着复杂性(complexity),使得代码bug率更高且难以阅读和维护。 这篇参考指南 (guide)的目的在于,通过详细描述应该做的(dos)和不应该做的(don’ts),来控制我们撰写C++代码时的复杂性。这些规则令程序员在高效 利用C++语言特性的同时,还能让手工写出的所有代码(code base)易于管理。 风格(style),或者可 读性(readability),我们把它称作为“用来管理我们C++代码的常用惯例(convention)”。不过用“风格”这个术语有些不当,因为 这些惯例并不只是指代码源文件的格式。 让代码可管理,一种方式是加强代码的一致性(consistency)。令任何一个程序员能够快速的理解另一个程序员所写的代码是非常重要的。如果 代码保持统一的风格并且遵循惯例,我们可以更加轻松的使用“模式匹配”(pattern- matching)推断出,不同的符号分别代表什么。创造这种代码公共性(common)需要习语(idiom)和模式(pattern),使得代码易于 理解。在某些情况下,也许有好的理由来改变某些风格规定,但为了获得一致性,不管怎样我们还是得保持风格。 这篇参考指南所关注的另一点是C++的功能膨胀问题。C++是一个有很多高级特性的庞大语言。在某些情况下,我们限制甚至禁止使用一些特性。这样做 是为了保持代码的简洁,并避免因使用这些特性而带来各种常见的错误和问题。指南里会列出这些特性,并解释为何限制使用它们。 Google 所开发的开源项目遵守这篇参考指南里所列出的规定。 注意,这篇指南不是C++教程,我们认为所有正在阅读的同志们都是熟悉C++的。 头文件(Header Files) 一般而言,每个.cc文件都有一个关联.h文件。但也有一些例外,比如用于单元测试(unit test)的.cc文件或者只包括一个main()函数的小型.cc文件。 正确的使用头文件可以让代码的可读性、规模(scale)和性能(performance)得到很大的改观。 接下来介绍的一些规则将会引导你越过使用头文件时的各种陷阱。 #define防护(The #define Guard) 所有的头文件都应该使用#define防护来防止多重包含(multiple inclusion),格式是 1<PROJECT>_<PATH>_<FILE>_H_ 为了保证唯一性(uniqueness),该格式应该基于源文件在整个项目中的路径。比如说,项目foo中的头文件foo/src/bar/baz.h应该用如下的格式: 123456#ifndef  FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ … #endif //  FOO_BAR_BAZ_H_ 头文件依赖(Header [...]