注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

关于使用DLL导入模板的内存泄露问题  

2012-12-14 14:30:57|  分类: C++(VC)编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
 

关于使用DLL导入模板的内存泄露问题

在一个导入库的工程中,使用了一个导入DLL的模板框架,这个网上有介绍先转一下然后再分析:

C++模板实现类注册和获取

分类: C++ 2008-04-27 10:17 440人阅读 评论(1) 收藏 举报

转:http://www.wangchao.net.cn/bbsdetail_65372.html

 

最近在做一些项目的时候,经常需要用到类的动态注册和获取,使得调用者可以根据名字创建一个类的实例,然后调用改类的虚函数完成功能调用,类似于建立一套数据库访问套件,可以做到后期增加何种数据库访问的无需修改原有代码,需要做的仅仅是按照一定的规范创建一个新类,该类会完成自身的注册,调用者根据名字获取改类实例即可,涉及到的技术包括模板、singletons、object factory方,下面将涉及到的类列在下面并进行一些描述:

  // ClassRegister.h: interface for the CClassRegister class.

  //

  //////////////////////////////////////////////////////////////////////

  #if !defined(AFX_CLASSREGISTER_H__809D89C4_E53F_43CE_8DE4_37248A855CCB__INCLUDED_)

  #define AFX_CLASSREGISTER_H__809D89C4_E53F_43CE_8DE4_37248A855CCB__INCLUDED_

  #ifdef _WIN32

  // 这里假设windows平台上使用的编译器是VC,由于VC自身携带的STL实现会导致

  // 使用map时出现大量的警告,因此关闭这个警告

  #pragma warning (disable:4786)

  #endif

  // 引入必要的头文件

  #include <map>

  #include <string>

  #include <assert.h>

  namespace michael

  {

  /**

  * 当找不到指定的对象时的缺省处理方法,缺省方法就是抛出一个异常

  */

  template<class _IdentifierType, class _ClassType>

  class CDefaultFactoryError

  {

  public:

  /**

   * 从基本的异常类继承,用以实现无法找到对象时的异常抛出

   */

  class Exception : public std::exception

  {

  public:

   Exception(const _IdentifierType &unknownId)

   : m_unknownId(unknownId)

   {

   }

   virtual const char *what() const

   {

   return "Unknown Object type passed to Factory.";

   }

   const _IdentifierType &getId()

   {

   return m_unknownId;

   }

  private:

   _IdentifierType m_unknownId;

  };

  protected:

  virtual _ClassType *OnUnknownType(const _IdentifierType &id) const

  {

   throw Exception(id);

  }

  };

  /**

  * 这里提供了另外一种常见的处理方法,即找不到对象时返回NULL,

  * 可以通过模板参数来替换,但是下面的注册和创建宏则无法使用

  * 需要自己写代码或者宏

  */

  template<class _IdentifierType, class _ClassType>

  class CNullFactoryError : public CDefaultFactoryError<_IdentifierType, _ClassType>

  {

  protected:

  virtual _ClassType *OnUnknownType(const _IdentifierType &id) const

  {

   return NULL;

  }

  };

  /**

  * 类工厂的实现类

  * 有四个模板参数

  * 第一个模板参数是抽象类

  * 第二个模板参数是标识符的类型,这里采用字符串,也就是说用名字可以获取类实例

  * 第三个模板参数是创建实际对象的时候调用的方法或者访函式

  * 第四个模板参数是类工厂的基类,用于处理根据名字无法创建对象时的处理方法

  */

  template

  <

  class _AbstractClass,

  class _IdentifierType = std::string,

  class _ProductCreator = _AbstractClass* (*)(),

  class _FactoryErrorPolicy = CDefaultFactoryError<_IdentifierType, _AbstractClass>

  >

  class CClassFactory : _FactoryErrorPolicy

  {

  public:

  /**

   * 用于对类进行注册

   */

  bool Register(const _IdentifierType &id, _ProductCreator creator)

  {

   return m_associations.insert(AssocMap::value_type(id, creator)).second;

  }

  /**

   * 取消注册

   */

  bool Unregister(const _IdentifierType &id)

  {

   return m_associations.erase(id);

  }

  /**

   * 根据标识符创建对象实例,并已抽象类指针的方式返回

   */

  _AbstractClass *CreateObject(const _IdentifierType &id) const

  {

   AssocMap::const_iterator it = m_associations.find(id);

   if ( m_associations.end() != it )

   {

   return (it->second)();

   }

   return OnUnknownType(id);

  }

  /**

   * 类工厂对象的实例

   */

  static CClassFactory *Instance()

  {

   static CClassFactory<_AbstractClass, _IdentifierType,

   _ProductCreator, _FactoryErrorPolicy> inst;

   return &inst;

  }

  public:

  typedef std::map<_IdentifierType, _ProductCreator> AssocMap;

  AssocMap m_associations;

  CClassFactory(){}

  CClassFactory(const CClassFactory &other){}

  virtual ~CClassFactory(){}

  CClassFactory &operator=(const CClassFactory &other){}

  };

  #ifndef DYNAMIC_REGISTER_CLASS

  /**

  * 这里尝试用一个仿函式来替代下面宏生成的createObject但是还没有解决

  * 多模板类之间数据共享的问题

  */

  template <class T>

  class SimpleCreater

  {

  public:

  T *operator()() const

  {

   return new T();

  }

  };

  /**

  * 定义了一个宏,将这个宏签入到具体实现的类中,为类增加一个静态函数

  * 该函数具体创建对象的实例,两个模板参数分别时真实的动态注册类和

  * 抽象类

  * 该宏需要签入到类的public部分

  */

  #define DYNAMIC_CREATE_CLASS(cls, abstract) static abstract *createObject() { return new cls(); }

  /**

  * 定义一个宏自动完成类的注册

  * 该宏需要插入在类的实现文件中

  */

  #define SIMPLY_DYNAMIC_REGISTER_CLASS(abstract, cls, strname) const bool bRet = michael::CClassFactory<abstract>::Instance()-> Register(strname, cls::createObject);

  /**

  * 定义一个宏可以根据名字获取一个对象的实例

  */

  #define SIMPLY_CREATE_OBJECT(abstract, strname) michael::CClassFactory<abstract>::Instance()-> CreateObject(strname);

  /************************************************************************/

  /* 下面的两个宏根上面的功能相同只不过他们采用了另外一种错误处理方式 */

  /* 当无法招到对象时返回NULL,而不是抛出异常

  /************************************************************************/

  /**

  * 定义一个宏自动完成类的注册

  * 该宏需要插入在类的实现文件中

  */

  #define SIMPLY_DYNAMIC_REGISTER_CLASS_NULL(abstract, cls, strname) const bool bRet = michael::CClassFactory< abstract, std::string, abstract* (*)(), michael::CNullFactoryError<std::string, abstract> >::Instance()-> Register(strname, cls::createObject);

  /**

  * 定义一个宏可以根据名字获取一个对象的实例

  */

  #define SIMPLY_CREATE_OBJECT_NULL(abstract, strname) michael::CClassFactory< abstract, std::string, abstract* (*)(), michael::CNullFactoryError<std::string, abstract> >::Instance()-> CreateObject(strname);

  #endif

  }

  #endif // !defined(AFX_CLASSREGISTER_H__809D89C4_E53F_43CE_8DE4_37248A855CCB__INCLUDED_)

  下面结合例子说明一下如何使用:

  下面声明一个抽象类他定义了一些方法但是没有任何实现,下面的例子非常简单无法进行真正的应用,目的是说明上面的动态注册方法

  // 头文件 Database.h

  class CDatabase

  {

  public:

  virtual void open() = 0;

  virtual void close() = 0;

  };

  // 头文件 OracleDB.h

  class COracleDB : public CDatabase

  {

  public:

  // 这里说明了COracleDB类需要动态注册,并在内部实现了createObject函数

  DYNAMIC_CREATE_CLASS(COracleDB , CDatabase)

  virtual void open();

  virtual void close();

  }

  // 实现文件COracleDB.cpp

  #include "OracleDB.h"

  // 这个宏完整了真正的注册

  SIMPLY_DYNAMIC_REGISTER_CLASS(CDatabase, COracleDB, "oracle")

  void COracleDB::open()

  {

  // 一些实现

  }

  void COracleDB::close()

  {

  // 一些实现

  }

  上面的代码完成后,真正的调用者就可以根据字符串获取类的实例了

  int main()

  {

   CDatabase *pDb = SIMPLY_CREATE_OBJECT(CDatabase, "oracle");

   pDb->open();

   pDb->close();

   return 0;

  }

  当后期需要增加对mysql支持的时候可以按照上面oracle的方法实现一个mysql的类,然后在主函数里面更换一下类名即刻,无需任何实质的改动,更可以通过配置文件或者主函数的传入参数来说明数据库类别,距离如下:

  int main(int argc, char **argv)

  {

   if ( argc != 2 )

   {

   return -1;

   }

   CDatabase *pDb = SIMPLY_CREATE_OBJECT(CDatabase, argv[1]);

   pDb->open();

   pDb->close();

   return 0;

  }

  如上即实现了动态注册和获取,并且这部分代码是完全C++标准的,不依赖于MFC等类库,在跨平台应用中可以起到很好的作用,因为涉及到的理论很多,而且都比较庞大,这里就没有介绍原理,而仅仅是说明如何使用,感兴趣的朋友可以自己看一下关于模板、singletons、object factory方面的知识。:)

希望能够给大家一些启事,环境交流,呵呵。

--------------------------------------------------------------------------------------------------------------------------------

转载到这里结束,开始正题,之所说有面用这么一大幅的文字来说明这个模板的设计方法,是觉得这个模板写得还是很有可取之处。但其中的一些不足也非常明显。

从实际产生的问题及解决方法一点点一讲,大家就明白了,在项目中发现,程序在运行时会造成内存的快速泄露,导致内存的占用率居高不下,最后定位到下面的语句:

                   IClassTypeFactory *pFactory;

                   pFactory = SIMPLY_CREATE_OBJECT(IClassTypeFactory, classTypeIt->second.c_str());

通过内存的调用堆可以看出:

_AbstractClass *CreateObject(const _IdentifierType &id) const

{

 AssocMap::const_iterator it = m_associations.find(id);

 if ( m_associations.end() != it )

 {

          return (it->second)();

 }

 return OnUnknownType(id);

}

红色部分是他的下一层栈,再下一层为:

DYNAMIC_CREATE_CLASS(CClassTypeFactoryNormal, IClassTypeFactory,onlyself)

其实在内存中你可以看到,这行代码是转化成实际代码的函数。也就是:createObject

这个函数。

那么大家就很明白了,这个模板并不是一开始就注册一个类的实例对象到map中去,而是注册了一个创建器到其中,这个一开始忽略了。

解决问题的过程一开始是在:

 static CClassFactory *Instance()

  {

   static CClassFactory<_AbstractClass, _IdentifierType,

   _ProductCreator, _FactoryErrorPolicy> inst;

   return &inst;

  }

当时认为可能是静态局部变量的反复创建,觉得有些不妥,果然在C++PRIMER上写着静态局部变量是不反复分配而是一次分配,多次使用,但只有在进入到所属函数时,才会重新可用,静态的局部变量会在程序结束后回收内存

这样就想把这个静态变量拿出来,像单实例一样处理一下,结果这遇到了麻烦,模板往外弄不容易啊,最后如下搞定:

声明(.h文件):

static CClassFactory<_AbstractClass, _IdentifierType,

 _ProductCreator, _FactoryErrorPolicy> inst;

初始化(类外CPP):

template

class _AbstractClass,

class _IdentifierType ,

class _ProductCreator,

class _FactoryErrorPolicy

CClassFactory<_AbstractClass, _IdentifierType,_ProductCreator, _FactoryErrorPolicy>   

   CClassFactory<_AbstractClass, _IdentifierType,_ProductCreator, _FactoryErrorPolicy>::inst;

主要的问题是初始化,总是报只有模板声明时才可以带参数默认值,所以把默认参数去掉就好了,另外就是两个都得带参数,不熟悉的可以去看模板的资料。

结果这样弄好后,调试发现,返回的内存的地址始终是不变的,这样就说明找到的位置不对,然后才发现了前述讲的堆栈的调用过程,才发现原来是反复的NEW对象造成的,这里做了一个实验,在程序中做了一个大的循环,结果瞬间内存就要挂掉的意思。说明内存泄露确实是很严重。

那问题找到了,就要进行问题解决了,两个方向,一个是在模板类里动作,这样改动小,影响小。第二是从应用的代码改,改动大,而且容易引起其它连锁的反应。

其实从这个程序的应用来看,大家已经发现,这个程序只进行了内存的分配,并没有内存的管理,也就是说,他只能应用到分配次数较少的调用过程中。最重要的是,这里并没有提示。

从上层修改,其实也不难,主要是有影响,改动的方法是对实例对象反回的指针进行判断,如果不为空,就不再判断了。

从内部修改,可以用单例的方法:

注册类增加:

static CClassTypeFactoryNormal* onlyself;    //单例变量

DYNAMIC_CREATE_CLASS(CClassTypeFactoryNormal, IClassTypeFactory,onlyself)

 

修改宏定义:

#define DYNAMIC_CREATE_CLASS(cls, abstract,a) static abstract *createObject() { if (0 ==a){ a=new cls();} return a; }

注意上面的宏定义要在一行上,否则要加上\连接字符。

这样基本就可以了。

通过上面的分析可以发现,使用模板的缺点,复杂,不容易跟踪,象这个又和宏配合在一起,更加难以控制。不过,话说回来,瑕不掩瑜,应用得体,还是会起到相当大的功效的,所以还是那句话,语言,方法无所谓绝对的好坏,看你应用的具体的场景。

反对经验主义,教条主义,本本主义和形而上。

认真学习,勇于思考,大胆分析,努力实践。

  评论这张
 
阅读(914)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017