衆所周知,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的視窗和渲染器的建立。