COCOS2D-X中的智能指针

Cocos2d-x中所有内存管理方式的基础是引用计数,动态分配一个Ref对象后其引用计数为1,并通过retain和release来增持和减少其引用计数。引用计数本身并不能帮助我们进行内存管理。

为了正确地释放对象的内存,Cocos2d-x使用Objective-C里面的自动回收池的机制来管理对象内存的释放。Autorelease有点类似于一个共享的”智能指针”,该”智能指针”的作用域为一帧,该帧结束后,它将释放自己的引用计数,此时,如果该对象没有被其他”共享指针”引用,则对象被释放。如果对象被引用,则保留。

Vector和Map<K,V>通常用来和autorelease一起工作,我们通常应该将一个autorelease对象加入到Vector或者Map中,例如Node将所有的子元素存储在一个Vector<Node*>中。Vector和Map对新加入的元素执行retain操作,并对从中移除的元素执行release操作,这样元素在从Vector或者Map中移除的时候就会被自动释放。

对于单个的非集合元素对象,我们往往不会通过autorelease来进行管理,除非它是一个临时对象。这个时候我们只能手动使用retain和release来进行管理,这其实等价于通过new和delete来进行内存管理,这样的情况就容易导致内存管理问题。

因此,Cocos2d-x 在3.1中引入了智能指针RefPtr,RefPtr是基于RAII[引用6]实现的,RAII的全称为Resource Acquisition Is Initialization,是由C++之父Bjarne Stroustrup提出的管理动态内存的方法。在RAII中,动态资源的持有发生在一个对象的生命周期之内,即是说在对象的构造函数中分配内存,在对象的析构函数中释放内存。这就是我们前面讲述的将动态分配的内存映射到一个自动变量上,通过自动变量的构造函数和析构函数来分配和释放内存。这可以保证资源始终会被释放,即使出现异常,也能被正常释放。这也是各种智能指针如std::shared_ptr实现的基本原理。

RefPtr实际上是模仿C++11中的std::shared_ptr实现的,它保持着一个Ref*对象的强引用,并使用Cocos2d-x自身的引用计数来管理多个智能指针对内存的共享。与shared_ptr相比,它更轻量级,并且能够结合Cocos2d-x的内存管理模型,但是它不保证线程安全,因此比shared_ptr更高效。但是,Cocos2d-x并没有提供与std::unique_ptr和std::weak_ptr类似功能的智能指针。

3.2.6.1 构造函数

RefPtr需要依赖于Ref的引用计数来管理内存,所有类型T必须是一个Ref类型,Cocos2d-x通过静态转换static_const来在编译时进行类型检查。

RefPtr提供了几个重载的构造函数,由于RefPtr变量和Ref指针是一种强引用关系,所以这些构造函数会对任何不为nullptr的Ref指针增持其引用计数,除非它是一个右值。例如:

// 转换函数

RefPtr<__String> ref2(cocos2d::String::create(“Hello”));

CC_ASSERT(strcmp(“Hello”, ref2->getCString()) == 0);

CC_ASSERT(2 == ref2->getReferenceCount());

// 复制构造函数

RefPtr<__String> ref4(ref2);

CC_ASSERT(strcmp(“Hello”, ref4->getCString()) == 0);

CC_ASSERT(3 == ref2->getReferenceCount());

CC_ASSERT(3 == ref4->getReferenceCount());

在C++中,只有一个参数的构造函数可以看做一个转换函数,在上面的例子中,类型T*的转换函数对T*引用计数执行了+1,而对于左值的ref2使用的复制构造函数也会对引用的内存执行引用计数+1。通过复制构造函数和转换函数,多个RefPtr可以共享一个Ref对象,并且它们各自均保持对Ref的强引用关系。

而对于右值的复制构造函数则不会增加其引用计数,因为通常对于返回右值的方法,该方法通常不再负责对该对象的内存进行管理,这个时候接受者不应该是共享的一方,而应该是将其对内存的占用转移过来,例如:

RefPtr<__String> getRefPtr()

{

RefPtr<__String> ref2(cocos2d::String::create(“Hello”));

CC_ASSERT(strcmp(“Hello”, ref2->getCString()) == 0);

CC_ASSERT(2 == ref2->getReferenceCount());

return ref2;

}

// 移动复制构造函数

RefPtr<__String> ref4(getRefPtr());

CC_ASSERT(strcmp(“Hello”, ref4->getCString()) == 0);

CC_ASSERT(2 == ref4->getReferenceCount());

方法getRefPtr()返回一个右值的RefPtr<__String>智能指针,移动复制构造函数被调用,对返回对象的内存的管理被转移而不是被共享,其不会增加右值的引用计数。

此外,我们可以使用三种特殊的方式来构造一个空的智能指针:

// 默认构造函数

RefPtr ref1;

CC_ASSERT(nullptr == ref1.get());

// 使用空指针参数构造

RefPtr<__String> ref3(nullptr);

CC_ASSERT((__String*) nullptr == ref3.get());

// 使用空引用的智能指针复制构造

RefPtr ref5(ref1);

CC_ASSERT((Ref*) nullptr == ref5.get());

3.2.6.2 赋值操作符

与构造函数类似,任何左值变量的赋值,RefPtr都应该与该左值共享资源而增持其引用计数,而对于右值,仍然应该使用转移而不是共享。与构造函数不同的是,赋值操作符除了会增持其资源的引用计数,还会释放对之前旧的资源的引用计数。

前面RefPtr定义了一个对类型T*的转换函数,在C++中,该转换函数会被用来执行强制转换或者赋值的隐士转换,例如:

RefPtr<__String> ptr=cocos2d::String::create(“Hello”);

实际上会调用T*到RefPtr的转换构造函数,这却不是我们想要的,因为ptr变量可能正持有其他的资源。因此RefPtr提供了对T*的赋值操作符重载:

template class RefPtr

{

public:

inline RefPtr & operator = (T * other)

{

if (other != _ptr)

{

CC_REF_PTR_SAFE_RETAIN(other);

CC_REF_PTR_SAFE_RELEASE(_ptr);

_ptr = const_cast::type*>(other);

}

return *this;

}

};

使得在对T*进行转换的时候不会直接调用转换方法,这样就可以对旧的资源进行释放。此外,也可以使用nullptr来让RefPtr成为一个空的智能指针。

3.2.6.3 弱引用赋值

不管是复制构造函数,还是赋值操作符,RefPtr会对任何非空的左值的资源保持一种强引用的关系。而有时候对于左值的资源我们仍然可能希望保持一种弱引用关系,例如:

RefPtr image;

image = new cocos2d::Image();

image->release();

如果对于左值的image对象,可以基于弱引用来构造智能指针,则会使得语法大大简化同时不容易出错。RefPtr通过提供一个weakAssign方法来实现弱引用:

template class RefPtr

{

public:

inline void weakAssign(const RefPtr & other)

{

CC_REF_PTR_SAFE_RELEASE(_ptr);

_ptr = other._ptr;

}

};

所以前面的例子就可以转换成下面简洁且不容易出错的写法:

RefPtr image;

image.weakAssign(new cocos2d::Image());

细心的朋友马上会发现,直接使用new Image()作为参数会导致转换函数的调用,而转换函数会增持其引用计数,那么这里的new Image()的引用计数仍然会被增加才对。然而实际这里的执行过程可以转换为如下的语句:

RefPtr image;

RefPtr temp(new Image()); //转换构造函数,引用计数为2

image.weakAssign(temp); //引用计数为2

其中,temp为weakAssign方法作用域内的自动变量,当weakAssign方法执行完毕后,temp临时变量将被销毁,从而执行析构函数,释放其对资源的占用,使其引用计数变为1.

3.2.6.4 其他操作

RefPtr其他一些操作包括析构函数中释放资源,这也是遵循RAII原则在对象的生命周期结束时释放资源。也可以通过调用reset方法来释放对其资源的占用,使其变为一个空的智能指针。

此外,RefPtr重载了*操作符,使其能够直接访问资源的地址,另外也可以通过get方法来访问资源的地址。

对于智能指针,比较常用的方法还包括对资源有效性的判断,我们可以通过将get方法得到的结果和nullptr进行比较来判断智能指针的有效性,另外RefPtr也重载了bool()操作符,使得我们可以直接判断其有效性,例如:

RefPtr<__String> ref1 = __String::create(“Hello”);

CC_ASSERT(true == (bool) ref1);

ref1 = nullptr;

CC_ASSERT(false == (bool) ref1);

RedPtr还包含一些对比较操作符的重载,类型的转换,这里不在详述,读者可以自行查看源代码。

3.2.6.5 RefPtr与容器

如果将一个元素加入到容器中,它还需要结合容器对内存的使用进行内存管理,那么RefPtr能否直接加入Vector和Map容器,答案是肯定的。

前面我们讲述了RefPtr提供了一个转换构造函数,用于将一个T*转换为RefPtr,实际上RefPtr还提供了一个到T*的转换操作符:

inline operator T * () const { return reinterpret_cast<T*>(_ptr); }

而Vector的pushBack方法接收一个T的指针,这样operator T*将会被自动调用加入到Vector,加入到Vector的元素的内存也受Vector进行共享管理,如下的代码:

auto str=new __String(“Hello”);

RefPtr<__String> ref1=str;

Vector<__String*> v;

v.pushBack(ref1);

这样RefPtr可以同时结合Cocos2d-x中的容器一起管理内存,使得对内存的管理更加灵活。当然你也可以直接使用*操作符或者get方法直接获取资源的地址传递给Vector,这里只是简化了操作。

3.2.6.6 RefPtr与自动回收池的比较

到此为止,Cocos2d-x提供了两种方式选管理内存的释放:autorelease和RefPtr,那么我们该怎么选择使用这两种内存管理方式呢?

为了比较他们之间的优势和用途,我们反过来尝试用彼此来代替对方。首先我们用autorelease来代替RefPtr,由于它完全依赖于自动回收池的释放,各个共享的变量几乎完全没法控制对资源的使用。

如果用RefPtr来代替autorelease,那么任何一个对Node资源的引用都是强引用,使得当Node从UI树中移除时我们还需要使用reset释放其对Node资源的占用,这显然是不可控制的。

因此,对于UI元素,我们完全需要使用一种弱引用类型的内存管理,只有UI树本身才可以分配和释放内存,其他任何地方都只能是弱引用。虽然RefPtr提供了弱引用赋值,但是RefPtr不能跟Vector有很好的协作。用RefPtr来管理UI元素会变得极其复杂。

所以,对于这两种内存管理方式,笔者的建议是,所有的UI元素都需要使用autorelease来管理,而游戏中的数据则使用智能指针RefPtr。

3.2.6.7 RefPtr的缺陷

Cocos2d-x中的智能指针也存在着一些缺陷,这些缺陷不是明显的,但是对其机制不熟悉的开发者也有可能遇到这些困惑。

首先是引用计数可以被RefPtr外部控制。例如:

auto str=new __String(“Hello”);

RefPtr<__String> ptr;

ptr.weakAssign(str);

str.release();

(*ptr)->getCString(); //访问野指针,将会报错

由于外部可以修改引用计数,将会使得RefPtr中资源的情况变得很复杂,它可能已经被释放,从而其构造函数对其进行释放的时候导致运行时错误,开发者需要谨慎地使用手动内存管理和智能指针的结合。这种情况在std::shared_pre中则不存在,因为开发者没法在外部修改引用计数。

其次,虽然RefPtr提供了一种弱引用,但是这个弱引用的智能指针仍然表现为一个强类型智能指针的行为,它仍然可以对其资源进行修改,从而导致原智能指针的行为变得不可预期。例如:

RefPtr<__String> ptr1(new __String(“Hello”)); //引用计数2

RefPtr<__String> ptr2;

ptr2.weakAssign(ptr1); //引用计数2

ptr2.reset(); //引用计数1

ptr2.reset(); //被释放

(*ptr1)->getCString(); //导致错误

在C++11中,弱引用的std::weak_ptr被限制只能通过其lock成员来访问原std::shared_ptr变量,从而对资源内存进行操作。这样能保证智能指针的有效性。而在Cocos2d-x中,我们则需要小心的保证智能指针的合法性。这在一定程度上给开发者带来困惑。

大家的以后到的个人博客 www.sundaboke.com  首先发布

时间: 2024-10-14 12:12:44

COCOS2D-X中的智能指针的相关文章

C++中的智能指针

众所周知.C++中对堆内存的申请与释放全然由用户来控制,这就造成用户在使用的时候常常造成内存泄漏.野指针.反复释放等常见的挂掉问题,所以我们有必要提供一套机制.使得用户仅仅需申请对应的内存,不用管释放的问题,事实上这属于著名的RAII(Resource Acquisition Is Initialization)技术 .在C++中这样的技术称作"智能指针",C++中的智能指针技术越来越受到广泛应用.以下简要介绍下智能指针. 从以上描写叙述中能够看出,我们须要提供一套内存显式申请与隐式释

Android系统篇之----Android中的智能指针

一.前言 今天我们开启Android系统篇的文章了,其实一直想弄,只是之前一直没有太多深入的了解,最近又把这块拿出来好好看了一下,所以想从新梳理一下,来看看Android中的这块知识,首先我们今天来看一下:Android中的智能指针的概念,为什么说先看一下智能指针这个知识呢?因为我们在看Android源码的时候,会发现几乎好多地方都用到了这个东东,所以我们在介绍后面的知识点,先来看看这个吧. 二.问题 那么Android中的智能指针是个什么东西呢?我们知道Android用的Java语言开发的,J

Boost中的智能指针(转)

这篇文章主要介绍 boost中的智能指针的使用.(转自:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html) 内存管理是一个比较繁琐的问题,C++中有两个实现方案: 垃圾回收机制和智能指针.垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案. 1. 定义 一个智能指针就是一个C++的对象, 这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除.注意这个“其不需要的

OSG中的智能指针

在OpenSceneGraph中,智能指针(Smart pointer)的概念指的是一种类的模板,它针对某一特定类型的对象(即Referenced类及其派生类)构建,提供了自己的管理模式,以避免因为用户使用new运算符创建对象实例之后,没有及时用delete运算符释放对象,而造成部分内存空间被浪费的后果,也就是所谓的内存泄露错误. 由于OSG中与场景图形有关的大多数类均派生自Referenced类,因此OSG大量使用了智能指针来实现场景图形节点的管理.智能指针的使用为用户提供了一种自动内存释放的

ATL和vc++中的智能指针(分别是CComPtr和_com_ptr_t)

一.智能指针的概念 智能指针是一个类,不是指针,智能指针在所包含的指针不再被使用时候会自动释放该所包含指针所占用的系统资源,而不用手动释放. 原理:智能指针封装了包含指针的AddRef()函数和Release()函数,且在该类不被需要的时候在析构函数里调用包含指针的Release()函数释放包含指针的资源.因此实质是利用类的析构达到调用包含指针的Release()函数的目的. 二.VC++中的智能指针:_com_ptr_t _com_ptr_t实质是一个类模板.使用它时需要提供三个参数:接口的名

实战c++中的智能指针unique_ptr系列-- 使用std::unique_ptr代替new operator(错误:‘unique_ptr’ is not a member of ‘std’)

写了很多篇关于vector的博客,其实vector很便捷,也很简单.但是很多易错的问题都是vector中的元素为智能指针所引起的.所以决定开始写一写关于智能指针的故事,尤其是unique_ptr指针的故事. 这是个开始,就让我们使用std::unique_ptr代替new operator吧! 还是用程序说话: #include<iostream> int main() { while (true) int *x = new int; } 看下任务管理器中的内存: 此时使用智能指针unique

Boost库中的智能指针 shared_ptr智能指针

shared_ptr智能指针的意思即:boost::shared_ptr是可以智能的管理动态分配的内存资源,几个智能指针可以同时共享一个动态分配的内存的所有权. 下面我们通过一个例子来学习一下它的用法: 注 :使用shared_ptr智能指针,要加入#include <boost/shared_ptr.hpp>头文件 class example { public: ~example() { std::cout <<"It's over\n"; } void do

C++中的智能指针(auto_ptr)

实际上auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,使用它不必每次都手动调用delete去释放内存.当然有利也有弊,也不是完全完美的. 本文从下面的8个方面来总结auto_ptr使用的大部分内容. 1. auto_ptr是什么? auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个这样拥有者(auto_ptr).当auto_ptr对象生命

实战c++中的智能指针unique_ptr系列-- unique_ptr的operator=、operator bool、reset、swap、get等介绍

既然打算把unique_ptr写一个系列,就要详尽一点,有些内容也许在vector的时候有个涉及,但是现在还是再谈论一番. 我们要把unique_ptr看做一个类,废话了,它当然是一个类.所以这个类肯定也重载了赋值运算符,即operator=.现在就开始看看operator=在unique_ptr中的使用: 官方描述如下: move assignment (1) unique_ptr& operator= (unique_ptr&& x) noexcept; assign null