悟云

let it be

0%

关于do-while,笔者之前一直停留在学习c++基础语法时的理解,然而后面读框架代码时经常看到do…while(0)这样的用法,当时不理解这样用的用意。
纸上得来终觉浅,绝知此事要躬行。 当我们看技术书籍或者看一些优秀的项目源码时,对于很多小地方可能也就是一目置之,自己亲身经历一次才会深刻的明白,这里也提醒自己以后多多注意细节。
最近在实现一个类的初始化函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool MapFactory::init()
{
bool ret = false;
ret = initSimple();
if(!ret){
return false;
}

ret = initMiddle();
if(!ret){
return false;
}

ret = initHard();
if(!ret){
return false;
}

ret = initSpecial();
if(!ret){
return false;
}

return ret;
}

当时笔者在写上面这段代码时就感觉好麻烦,好多的return语句。
然而,我们可以巧妙地借用下循环里break的思路,优化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool MapFactory::init()
{
bool ret = false;
do{
ret = initSimple();
if(!ret){
//return false;
break;
}

ret = initMiddle();
if(!ret){
//return false;
break;
}

ret = initHard();
if(!ret){
//return false;
break;
}

ret = initSpecial();
if(!ret){
//return false;
break;
}
}while(0);

return ret;
}

当然上面的例子并没有恰当地表现出这个技巧的实用之处,因为上面的代码在每一次调用之时无外乎失败就直接返回,但是假如子初始化函数失败还需要释放资源,比如指针之类的。嗯,自行体会。

之前在阅读的代码中看到的do…while(0)代码更为常见的是红定义中,参加下面代码:

1
#define SAFE_DELETE(p) do{ delete p; p = NULL;} while(0)

假设这里去掉do…while(0),

1
#define SAFE_DELETE(p) delete p; p = NULL;

注意下面宏调用:

1
2
if(NULL != p) SAFE_DELETE(p)
else do something

这里如果是下面的定义方式,程序编译会报错,这里的do-while相当于提供了一种健壮的宏定义方式,至于个中细节,自行体会。
最后我想说的是,程序就像是一件艺术品,没有最好的代码实现, 只有更好,更优雅的代码。只有在一次又一次的实战中不断总结,才能不断提升自己。

今天在设计一个随机数时遇到了一些问题:

1
2
vector<int> array(10);
int index = floor(CCRANDOM_0_1() * array.size)

就是xcode里上面简单的几行代码,让我纠结很久。经过测试,主要原因是,xcode里float或者double转换成int时不能正确转换,至于原因,在下搜索很久都没找到答案, 暂时就这样不了了之吧。
嗯,于是发现了c++ 11关于随机数的新特性,上述代码改为如下:

1
2
3
4
5
6
#include <random>

srand(time(NULL));
std::default_random_engine e;
std::uniform_int_distribution<unsigned> u(0, array.size());
auto index = u(e);

路漫漫其修远兮。

昨天晚上第一次开始着手将cocos2dx project打包apk,在此记录下。

准备工作

jdk

下载jdk,并安装

ndk

下载ndk,并解压

android sdk

下载android, sdk并解压

ant

下载ant, 并解压

配置环境变量

进入cocos目录,运行setup.py,按提示操作即可。

编译

进入cocos工程主目录,利用cocos工具编译如下

1
cocos compile -p android --ap android-25#--ap代表sdk版本

上面的步骤会在cocos工程文件里生成一个apk文件,大功告成。

cocos2dx里的所有节点对象都继承自Ref
下面是Ref的源代码核心部分摘录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ref
{
public:
void retain();
void release();
Ref * autorelease();
unsigned int getReferenceCount() const;
protected:
Ref();
public:
virtual ~Ref();
protected:
unsigned int _referenceCount;
friend class AutoreleasePool;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Ref::Ref(): _referenceCount(1){}
Ref::~Ref() {}
void Ref::retain()
{
assert(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
void Ref::release()
{
assert(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if(_referenceCount == 0)
{
delete this
}
}
Ref * Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}

可以看到Ref主要就是运用了引用计数,在一个节点的create函数中通常都会都会调用autorelease,在这个函数中调用的是AutoreleasePool的addObject方法。
下面是AutoreleasePool以及PoolManager的源代码实现核心部分摘录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class AutoreleasePool
{
public:
AutoreleasePool();//warning Don't create an autorelease pool in heap, create it in stack.
AutoreleasePool(const std::string &name);
~AutoreleasePool();
void addObject();
void clear();
private:
std::vector<Ref *> _managedObjectArray;
std::string _name;
}
AutoreleasePool::AutoreleasePool() : _name("")
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
AutoreleasePool::AutoreleasePool(const std::string &name):_name(name)
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
AutoreleasePool::~AutoreleasePool()
{
clear();
PoolManager::getInstance()->pop();
}
void AutoreleasePool::addObject(Ref * object)
{
_managedObjectArray.push_back(object);
}
void AutoreleasePool::clear()
{
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
for (const auto &obj : releasings)
{
obj->release();
}
}

class PoolManager
{
public:
static PoolManager * getInstance();
AutoreleasePool * getCurrentPool() const;
friend class AutoreleasePool;
private:
PoolManager();
~PoolManager();
void push(AutoreleasePool * pool);
void pop();
static PoolManager * s_singleInstance;
std::vector<AutoreleasePool *> _releasePoolStack;
}
PoolManager * PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
s_singleInstance = new (std::nothrow) PoolManager();
// Add the first auto release pool
new AutoreleasePool("cocos2d autorelease pool");
}
return s_singleInstance;
}
PoolManager::PoolManager()
{
_releasePoolStack.reserve(10);
}
PoolManager::~PoolManager()
{
while(!_releasePoolStack.empty())
{
AutoreleasePool * pool = _releasePoolStack.back();
delete pool;
}
}
PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
}
void PoolManager::push(AutoreleasePool * pool)
{
_releasePoolStack.push_back(pool);
}
void PoolManager::pop()
{
assert(!_releasePoolStack.empty());
_releasePoolStack.pop_back();
}

总结:
1.autorelease()的实质是将对象加入自动释放池,对象的引用计数不会立刻减1,在自动释放池被回收时对象执行release()。
2.autorelease()只有在自动释放池被释放时才会进行一次释放操作,如果对象释放的次数超过了应有的次数,则这个错误在调用autorelease()时并不会被发现,只有当自动释放池被释放时(通常也就是游戏的每一帧结束时),游戏才会崩溃。在这种情况下,定位错误就变得十分困难了。例如,在游戏中,一个对象含有1个引用计数,但是却被调用了两次autorelease()。在第二次调用autorelease()时,游戏会继续执行这一帧,结束游戏时才会崩溃,很难及时找到出错的地点。因此,我们建议在开发过程中应该避免滥用autorelease(),只在工厂方法等不得不用的情况下使用,尽量以release()来释放对象引用。
3.autorelease()并不是毫无代价的,其背后的释放池机制同样需要占用内存和CPU资源。过多的使用autorelease()会增加自动释放池的管理和释放池维护对象存取释放的支出。在内存和CPU资源本就不足的程序中使得系统资源更加紧张。此时就需要我们合理创建自动释放池管理对象autorelease。
不用的对象推荐使用release()来释放对象引用,立即回收。