osge阶段学习总结3(给自己看的记录)——OSG 智能指针 详解
注:osg版本号为3.6.3
1、Referenced类
osg::Referenced类是几乎所有osg类的父类。
这个类与智能指针修改部分函数如下:(不相关函数略)
class OSG_EXPORT Referenced
{
public:
//ref()方法,在这个方法中只做了一件事,就是对_refCount进行++运算;
inline int ref() const;
/*unref()方法,在此方法中首先对_refCount进行--运算,之后再判断经过—运算之后的
引用计数是否等于0,若等于0(意味着没有对象引用此段内存空间,可以被释放)
则将needDelete赋值为true。在函数最后,对needDelete进行判断,如果需要被释放,
则调用signalObserversAndDelete(true,true)方法,在这个函数中将会具体执行对对象的删除。*/
inline int unref() const;
/*对引用计数执行–1 运算。不检查引用计数是否为零,
不进行内存空间delete或其它方式的内存释放*/
int unref_nodelete() const;
//引用计数值
inline int referenceCount() const { return _refCount; }
protected:
/*在这个函数中具体执行对对象的删除,它首先对其所有观察者进行删除
(应该是观察者模式之类的,暂时也不是太理解,可以暂时忽略,注意后面才是本节重点)。
之后再判断是否存在DeleteHandler,当我们需要自定义删除的时候,
我们可以自己写一个DeleteHandler实现我们想要的功能,
如果没有,则直接执行delete操作,对对象进行删除 */
void signalObserversAndDelete(bool signalDelete, bool doDelete) const;
};
Referenced类管理了一个_refCount成员变量,用于记录引用计数。其它函数都是对_refCount成员变量的“++”或“–”管理。所有继承该类的类都有一个引用计数以及相关功能。
2、ref_ptr类。
ref_ptr类是一个类,是一个模板类。
这个类与智能指针修改部分函数如下:(没有不相关函数,但省略一些非活动预处理代码)
template<class T>
class ref_ptr
{
public:
typedef T element_type;
//一系列的构造函数,第一个为默认构造函数,
ref_ptr() : _ptr(0) {}
/*第二个需要一个类型为“T*”类型的参数,即需要一个继承自Reference类的对象指针作为参数,
并且通过一个if语句判断是否赋值成功,如果成功则调用其所管理的对象指针的ref方法使引用计数加一。*/
ref_ptr(T* ptr) : _ptr(ptr) { if (_ptr) _ptr->ref(); }
//传入一个模板参数相同的ref_ptr对象,如果成功引用,则调用ref是引用计数加一
ref_ptr(const ref_ptr& rp) : _ptr(rp._ptr) { if (_ptr) _ptr->ref(); }
/*复制构造函数,将另一个ref_ptr对象作为参数传入当前对象中
——对所管理的指针增加一个引用,适用与模板参数不同的情况 */
template<class Other> ref_ptr(const ref_ptr<Other>& rp) : _ptr(rp._ptr)
{ if (_ptr) _ptr->ref(); }
//用于接受弱指针作为参数的构造函数。
ref_ptr(observer_ptr<T>& optr) : _ptr(0) { optr.lock(*this); }
/*析构函数,在此析构函数中,首先调用unref函数使得管理的指针中的引用计数减一,
之后将当前指针置空。*/
~ref_ptr() { if (_ptr) _ptr->unref(); _ptr = 0; }
//上面是构造函数、拷贝构造函数、复制构造函数和析构函数等///
/*重载赋值操作符,处理将一个智能指针赋值给另一个智能指针的情况,
与直接调用ref函数不同,在这个函数中通过调用assign函数来处理。
例如:osg::ref_ptr<osg::Node> node1 = new osg::Node;
osg::ref_ptr<osg::Node> node = node1;(ref_ptr都是管理的Node类对象) */
ref_ptr& operator = (const ref_ptr& rp)
{
assign(rp);
return *this;
}
/*重载赋值操作符,用于处理管理不同类型的智能指针之间的引用。
例如:osg::ref_ptr<osg::Geode> node1 = new osg::Node;
osg::ref_ptr<osg::Node> node = node1;(相当于将子类对象转化为父类对象)*/
template<class Other> ref_ptr& operator = (const ref_ptr<Other>& rp)
{
assign(rp);
return *this;
}
/*重载赋值操作符,用于处理将一个普通指针交由超级指针管理。例如:
osg::ref_ptr<osg::Node> node1 = new osg::Node;这不就和我们上面的例子中用到的一样么?
我们new了一个osg::Group,之后将其返回的地址通过这个等于符号传给创建的root对象,
在我们使用“=”运算符的时候不就是调用的这个函数么?*/
inline ref_ptr& operator = (T* ptr)
{
if (_ptr==ptr) return *this;
T* tmp_ptr = _ptr;
_ptr = ptr;
if (_ptr) _ptr->ref();
// unref second to prevent any deletion of any object which might
// be referenced by the other object. i.e rp is child of the
// original _ptr.
if (tmp_ptr) tmp_ptr->unref();
return *this;
}
//上面是重载的各种赋值函数///
#ifdef OSG_USE_REF_PTR_IMPLICIT_OUTPUT_CONVERSION
// implicit output conversion
operator T*() const { return _ptr; }
#else
.........
#endif
/*重载与普通指针相关的运算符,如‘*’、‘->’。使其具有指针的功能,
并设置一个get()方法用于获取智能指针类所管理的指针对象*/
T& operator*() const
{
#ifdef OSG_USE_REF_PTR_SAFE_DEREFERENCE
if( !_ptr ) {
// pointer is invalid, so throw an exception
throw std::runtime_error(
std::string("could not dereference invalid osg pointer ")
+ std::string(typeid(T).name()));
}
#endif
return *_ptr;
}
T* operator->() const
{
#ifdef OSG_USE_REF_PTR_SAFE_DEREFERENCE
if( !_ptr ) {
// pointer is invalid, so throw an exception.
throw std::runtime_error(
std::string("could not call invalid osg pointer ")
+ std::string(typeid(T).name()));
}
#endif
return _ptr;
}
T* get() const { return _ptr; }
//上面是重载的方便当作指针调用的函数///
/*重载运算符‘!’,并设置一个valid()方法用于判断智能指针类所管理的指针对象是有效,
有效则返回1,无效则返回0; */
bool operator!() const { return _ptr == 0; } // not required
bool valid() const { return _ptr!=0; }
/*release方法,用于在返回一个对象的时,并能防止内存泄露。
它与get方法最大的区别就是get方法直接返回所管理的指针,
而此处是先调用unref_nodelete()方法之后才返回其所管理的对象,这在后面会具体讲到。*/
T* release()
{
T* tmp = _ptr;
if (_ptr)
_ptr->unref_nodelete();
_ptr = 0;
return tmp;
}
//交换两个智能指针所管理的对象
void swap(ref_ptr& rp) { T* tmp=_ptr; _ptr=rp._ptr; rp._ptr=tmp; }
private:
//用于对指针引用时进行一些处理,对新添加的引用执行ref操作,而对以前的引用执行unref操作
template<class Other> void assign(const ref_ptr<Other>& rp)
{
if (_ptr==rp._ptr) return;
T* tmp_ptr = _ptr;
_ptr = rp._ptr;
if (_ptr) _ptr->ref();
// unref second to prevent any deletion of any object which might
// be referenced by the other object. i.e rp is child of the
// original _ptr.
if (tmp_ptr) tmp_ptr->unref();
}
//将其他模板参数的类设置为当前类的友元。
template<class Other> friend class ref_ptr;
T* _ptr;//私有变量,用于存放所管理的指针
};
虽然调用的时候把它看成一个指针完全可以。但是,ref_ptr是一个类,模板类。所以如下代码:
osg::ref_ptr<osg::Node> node = osgDB::readRefNodeFile("cow.osg");
其中,node是osg::ref_ptr类的对象,< osg::Node >是给模板类传递的类。类似模板类定义如下:
std::vector<int> intList;
其中,intList是std::vector的对象,< int >是给模板类传递的类。
3、调用实例说明。
调用实例代码如下:
osg::Node* create1()
{
osg::Node* nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp ;
}
osg::Node* creat2()
{
osg::ref_ptr<osg::Node> nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp.get();
}
osg::Node* creat3()
{
osg::ref_ptr<osg::Node> nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp.release();
}
int main()
{
osg::ref_ptr<osg::Node> node1 = create1();
osg::ref_ptr<osg::Node> node2 = create2();
osg::ref_ptr<osg::Node> node3 = create3();
osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(node1);//内存泄漏
root->addChild(node2);//野指针崩溃
root->addChild(node3);//正确。
osgViewer::Viewer* viewer = new osgViewer::Viewer;
viewer->setSceneData(root);
return viewer->run();
}
上面代码的说明:
3.1、create1()函数:
osg::Node* create1()
{
osg::Node* nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp ;
}
nodeTemp对象为osg::Node的指针对象,是局部变量。create1()函数外面,nodeTemp对象就析构了,析构的是指针,new的空间没有释放,出现内存泄漏。
3.2、create2()函数:
osg::Node* creat2()
{
osg::ref_ptr<osg::Node> nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp.get();
}
nodeTemp对象为osg::ref_ptr< osg::Node >的对象,是局部变量。具体点 nodeTemp是osg::ref_ptr的对象, < osg::Node >为给模板类传递的类。
create2()函数外面,nodeTemp对象就析构了,调用osg::ref_ptr类的析构函数。
/*析构函数,在此析构函数中,首先调用unref函数使得管理的指针中的引用计数减一,
之后将当前指针置空。*/
~ref_ptr() { if (_ptr) _ptr->unref(); _ptr = 0; }
其中,_ptr->unref(); 函数,在引用计数减一后,判断是否为零,为零进行delete操作清理内存。
/*unref()方法,在此方法中首先对_refCount进行--运算,之后再判断经过—运算之后的
引用计数是否等于0,若等于0(意味着没有对象引用此段内存空间,可以被释放)
则将needDelete赋值为true。在函数最后,对needDelete进行判断,如果需要被释放,
则调用signalObserversAndDelete(true,true)方法,在这个函数中将会具体执行对对象的删除。*/
inline int unref() const;
create2()函数外面,nodeTemp对象就析构了,这个对象的成员变量 _ptr 指针指向的new的空间也已经delete了。所以,这时node2对象中的成员变量 _ptr 指针指向的空间已经释放了,是野指针。会造成系统崩溃。
3.3、create3()函数:
osg::Node* creat3()
{
osg::ref_ptr<osg::Node> nodeTemp = osgDB::readNodeFile("cow.osg");
return nodeTemp.release();
}
其中,release函数,调用的是unref_nodelete()函数。
/*对引用计数执行–1 运算。不检查引用计数是否为零,
不进行内存空间delete或其它方式的内存释放*/
int unref_nodelete() const;
所以,create3()函数外面,nodeTemp对象就析构了,这个对象的成员变量 _ptr 指针指向的new的空间却没有delete,没有释放。所以,node3的成员变量 _ptr 指针指向的空间不是野指针。对node3赋值时候,对引用计数又加一。main函数退出时,引用计数为零时,可以检测删除。所以,也没有内存泄漏。完美؏؏☝ᖗ乛◡乛ᖘ☝؏؏
4、额外推荐
最后,推荐C++野指针实例的教程,说明的特别清楚明白十分经典。《高质量C/C++编程》(林锐博士)
本来 想放链接的,但是发现我竟然没有备份,而且,现在不好找了!原来哪里都有啊!
这里面有部分关于野指针的情况,最后的考卷和解答说的特别清楚明白。
5、最后
学习的时候查了很多别人写的。要是还有不明白的,强烈推荐下面这个blog,对于初学者来说讲得最清楚明白。
其中,有一个错误重复出现。就是他总说“内存泄漏”,但是我认为他很多说“内存泄漏”的地方都是野指针。内存泄漏的错误可能最后不用改,因为程序不大内存很多,但是野指针就是会程序崩溃的。其它的地方都很好。推荐推荐。
https://blog.csdn.net/u010133496/article/details/40215471
我感觉看这一个blog,osg智能指针的东西就全部明白了。我写的这个blog也是90%以上都是这里面的内容。我只是结合我应用的osg源码版本对代码部分进行了修改。按照我自己的理解方式和思维习惯记录的。为了以后忘记了速查用的。最近简直太能够忘东西了!(T_T)(T_T)
下面的blog是转载上面的blog,完全一样,但是我百度的时候,下面的链接排在前面,因为。。。代码都是黑底的,看着更加舒服!
https://blog.csdn.net/trevor_k/article/details/98472292
-------------------完结,好长,我好伟大!-------------------