SDL_Engine游戏引擎制作 1-C++的内存自动释放

众所周知,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的窗口和渲染器的创建。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值