众所周知,C++本身是不会自动释放new出来的对象的内存,即使没有指针引用它(此时的内存在程序运行期间将无法释放,导致了内存泄漏)。cocos2d-x给出的解决办法就是引用计数。SDL_Engine关于引用计数涉及到三个类,分别是:Object,PoolManager,AutoreleasePool。PoolManager是单例类,内有一个指向AutoreleasePool对象的指针(不同于cocos2dx),而AutoreleasePool有一个Object的指针数组,用来在一帧的结束时使得所有容器中的对象的引用数减少一。
先创建一个PlatformMarcos.h文件 包括一些基本的宏。
#ifndef __Platform_H__
#define __Platform_H__
#include<assert.h>
#include<functional>
#include<cstdio>
//namespace
#define NS_SDL_BEGIN namespace SDL{
#define NS_SDL_END }
#define USING_NS_SDL using namespace SDL;
#define CREATE_FUNC(__TYPE__) \
static __TYPE__*create() \
{ \
__TYPE__*pRet=new __TYPE__();\
if(pRet&&pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
delete pRet; \
pRet=NULL; \
return pRet; \
}
//断言
#define SDLASSERT(cond,msg) \
if(!(cond)) \
{ \
printf("Assert failed:%s",msg);\
assert(cond); \
}
#define SDL_SAFE_DELETE(p)do{if(p) delete p;p=nullptr;}while(0)
#define SDL_SAFE_RELEASE(p)do{if(p) p->release();}while(0)
#define SDL_SAFE_RELEASE_NULL(p)do{if(p) p->release(); p = nullptr;}while(0)
#define SDL_SAFE_RETAIN(p) do { if(p) { (p)->retain(); } } while(0)
#define SDL_BREAK_IF(cond) if(cond) break
#endif
PlantformMarcos.h文件包含了一些基本的宏,为方便以后的开发,后续也将会添加。需要注意的是,使用宏函数时应当添加尽可能多的小括号来保证其结果的正确性。
然后创建一个Object类,此类可以认为是java中的Object,以后的开发完全继承自该类,即万物皆为对象的思想。
#ifndef __Object_H__
#define __Object_H__
#include "PlatformMarcos.h"
NS_SDL_BEGIN
class AutoreleasePool;
/*拷贝接口*/
class Clonable
{
public:
virtual Clonable *clone()const=0;
//添加析构函数,不添加可能会发生内存泄漏
virtual ~Clonable(){}
};
class Object
{
protected:
//引用计数器
unsigned int _referenceCount;
//是否由内存管理器管理
bool _managed;
//唯一ID
unsigned int _uniqueID;
public:
Object();
virtual ~Object();
CREATE_FUNC(Object);
bool init();
//保留
void retain();
//释放
void release();
//自动释放
Object*autorelease();
//获得引用数量
unsigned int getReferenceCount() const{return _referenceCount;}
//获取当前的id
unsigned int getUniqueID()const{return _uniqueID;}
//是否交给释放池
bool isManaged(){return _managed;}
//友元
friend class AutoreleasePool;
};
NS_SDL_END
#endif
Clonable为抽象类,也可以认为是接口。严格地来说,c++中没有类似于java中的关键字来特地声明接口。
Object对象中含有一些威客保证能自动释放所必须的属性,引用计数的关键就在于retain(),release(),autorelease()函数。
#include "Object.h"
#include "PoolManager.h"
NS_SDL_BEGIN
Object::Object()
:_uniqueID(0)
{
//定义一个静态变量作为实例对象引用器,使得每个对象的id都唯一
static unsigned int uObjectCount=0;
uObjectCount++;
_referenceCount = 1;
_managed = false;
_uniqueID = uObjectCount;
}
Object::~Object()
{
//避免重复删除造成的错误
if(_managed)
{
PoolManager::getInstance()->removeObject(this);
}
}
bool Object::init()
{
return true;
}
void Object::retain()
{
SDLASSERT(_referenceCount > 0,"引用数不能小于0");
++_referenceCount;
}
void Object::release()
{
SDLASSERT(_referenceCount > 0,"引用数不能小于0");
--_referenceCount;
//如果引用计数为0 释放内存
if(_referenceCount == 0)
delete this;
}
Object* Object::autorelease()
{
if(_managed == false)
{
//加入自动释放池中
PoolManager::getInstance()->addObject(this);
_managed=true;
}
return this;
}
NS_SDL_END
如上所见,retain函数是使得引用计数加一,release函数则是减一。需要注意的是,当某个Object对象的引用为0时,就delete该对象,释放其内存。autorelease函数则是把当前的对象交给自动释放池,使得一帧结束后对内部包含的所有对象的引用计数全部减少一,并且不再管理此对象。比如,我在某处创建了一个Object* pObject = Object::create() (在create函数中内部调用了autorelease函数),然后没有调用该对象的retain函数,则在一帧结束后,PoolManager就会释放该内存。
#ifndef __AutoreleasePool_H__
#define __AutoreleasePool_H__
#include <vector>
#include <algorithm>
#include "Object.h"
NS_SDL_BEGIN
class AutoreleasePool : public Object
{
private:
std::vector<Object*> _managedObjects;
public:
AutoreleasePool();
virtual ~AutoreleasePool();
//insert different
void addObject(Object*pObject);
void removeObject(Object*pObject);
//清理容器
void clear();
};
NS_SDL_END
#endif
自动释放池保存着要管理的Object对象数组。
#include "AutoreleasePool.h"
NS_SDL_BEGIN
AutoreleasePool::AutoreleasePool(void)
{
}
AutoreleasePool::~AutoreleasePool(void)
{
}
void AutoreleasePool::addObject(Object*pObject)
{
_managedObjects.push_back(pObject);
}
void AutoreleasePool::removeObject(Object*pObject)
{
auto it = std::find(_managedObjects.begin(),_managedObjects.end(),pObject);
//没有该对象的引用,直接返回
if (it == _managedObjects.end())
return;
//不再被管理
pObject->_managed = false;
_managedObjects.erase(it);
}
void AutoreleasePool::clear()
{
for(auto it = _managedObjects.begin();it != _managedObjects.end();)
{
auto object = *it;
object->_managed = false;
object->release();
it = _managedObjects.erase(it);
}
}
NS_SDL_END
Autorelease类中用的最多的就是clear函数。即遍历数组,并进行release后从容器中删除此对象。
而PoolManager则相对简单,主要负责对Autorelease对象的管理,由于在SDL_Engine中,仅仅使用了一个Autorelease,所以该对象也可以删除。(为了扩展性而保留)
PoolManager为单例类。
#ifndef __SDL_PoolManager_H__
#define __SDL_PoolManager_H__
#include "Object.h"
NS_SDL_BEGIN
class AutoreleasePool;
class PoolManager:public Object
{
private:
static PoolManager* _pInstance;
private:
AutoreleasePool* _pCurReleasePool;
private:
PoolManager();
~PoolManager();
public:
static PoolManager*getInstance();
static void purge();
AutoreleasePool* getCurReleasePool();
void addObject(Object*pObject);
void removeObject(Object*pObject);
};
NS_SDL_END
#endif
#include "PoolManager.h"
#include "AutoreleasePool.h"
NS_SDL_BEGIN
PoolManager*PoolManager::_pInstance=NULL;
PoolManager::PoolManager()
{
//创建一个内存释放池
_pCurReleasePool=new AutoreleasePool();
}
PoolManager::~PoolManager()
{
//删除当前栈内容
_pCurReleasePool->clear();
//释放当前内存池
SDL_SAFE_RELEASE(_pCurReleasePool);
}
PoolManager*PoolManager::getInstance()
{
if(_pInstance==NULL)
{
_pInstance=new PoolManager();
}
return _pInstance;
}
void PoolManager::addObject(Object*pObject)
{
_pCurReleasePool->addObject(pObject);
}
void PoolManager::removeObject(Object*pObject)
{
SDLASSERT(_pCurReleasePool,"Object();");
_pCurReleasePool->removeObject(pObject);
}
AutoreleasePool*PoolManager::getCurReleasePool()
{
return _pCurReleasePool;
}
void PoolManager::purge()
{
//释放单例类
SDL_SAFE_DELETE(_pInstance);
}
NS_SDL_END
PoolManager中主要就是引用了AutoreleasePool类中的函数。
以上是为了实现引用计数的而进行的编写。接下来进行测试是否可以自动释放,由于此时没有使用到SDL,故当前还不存在帧的刷新,故本节的测试在控制台进行。这里建议下载一个内存泄漏检测工具,或者在Object的构造函数和析构函数中编写输出函数,来进行检测。
创建main.cpp
#include <iostream>
#include "SDL_Engine.h"
#include "vld.h"//内存泄漏工具对应的头文件,可删除
USING_NS_SDL;
using namespace std;
int main()
{
int key = 0;
Object* object = Object::create();
while ((key = getchar()) != 'q')
{
switch (key)
{
case '1':object->retain();break;
case '2':object->release();break;
default:
break;
}
PoolManager::getInstance()->getCurReleasePool()->clear();
}
PoolManager::purge();
_CrtDumpMemoryLeaks();//可删除
return 0;
}
在main函数一开始,创建了一个Object对象,为了模拟游戏中的帧,而采用了键盘响应。
测试一:输入除1,2的其他字符,然后再输入1或2,即报错(我的vs2012没有报错,但是如果设置断点的话,会发现该object对象的内存已经释放)
测试二:成对地输入1,2然后输入q,内存无泄漏。
测试三:只输入1,之后输入q,内存会发生泄漏。
本节代码链接:https://pan.baidu.com/s/1ow-z7HafqCblCfQ19DQkRw 密码:fuwd
下一节将会实现SDL的窗口和渲染器的创建。