`
美丽的小岛
  • 浏览: 310790 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

C++智能指针(补充QT的+shared_ptr陷阱)

    博客分类:
  • c++
 
阅读更多

 代码后面有说明,这里不啰嗦了。

#include "stdafx.h"
#include <memory>
#include <string>
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
    using namespace std;
   /*auto_ptr:会转让所有权,运行时崩溃********/
    auto_ptr<string> films[5] =
    {
        auto_ptr<string> (new string("A")),
        auto_ptr<string> (new string("B")),
        auto_ptr<string> (new string("C")),
        auto_ptr<string> (new string("D")),
        auto_ptr<string> (new string("E"))
    };
    auto_ptr<string> pwin;
    pwin = films[2];//Films[2]失去了对这个元素的所有权,后面的那句打印会出错的。
    //cout << *films[2] << endl;//这句会出错,运行时出错
    cout << *pwin << endl;

    /*unique_ptr:会转让所有权, 编译不过,及运行时崩溃********/
    unique_ptr<string> films02[5] =
    {
        unique_ptr<string> (new string("A")),
        unique_ptr<string> (new string("B")),
        unique_ptr<string> (new string("C")),
        unique_ptr<string> (new string("D")),
        unique_ptr<string> (new string("E"))
    };
    unique_ptr<string> pwin02;
    //pwin = films[2];//Films[2]失去了对这个元素的所有权,编译不过,如果unique_ptr右边是临时变量,可以赋值。
    pwin02 = move(films02[2]);//把所有权转让,用move是可以的。
    cout << *pwin02 << endl;
    //cout << *films02[2] << endl;//这个会运行时错误
    
    /**shared_ptr:引用计数********************/
    shared_ptr<string> films03[5] =
    {
        shared_ptr<string> (new string("A")),
        shared_ptr<string> (new string("B")),
        shared_ptr<string> (new string("C")),
        shared_ptr<string> (new string("D")),
        shared_ptr<string> (new string("E"))
    };
    shared_ptr<string> pwin03;
    pwin03 = films03[2];//films[2]中的引用对像加1
    for (int i = 0; i < 5; ++i)
    {
        cout << *films03[i] << endl;//
    }
    //释放的时候,会依次把对象的引用递减,减到O了就释放对象
    system("pause");
	return 0;
    /*总结:unique_ptr比auto_ptr更安全,推荐用。在使用容器时,这个要非常小心,特别是容器带有复制的,选择shshared_ptr用比较好一点*/
}

 

 

QT到底有多少种智能指针(smart pointer)

1.   QPointer :提供对指针的保护,当一个指针被删除以后,再使用不会造成野指针或者指针溢出。比如

QPointer<MyObj> obj …;
if(!obj.isNull()) obj->foo;// 
成功

… //
对象被另外一个线程删除了
if(!obj.isNull()) obj->foo;// 
不会造成内存错误,该函数不会被调用

(2009/10/27更正:需要加入if(!obj.isNull()) 进行判断,多谢linkerlin指出)

2.   QSharedDataPointer:提供对数据的COPY-ON-WRITE,提供对数据(注:请区别于指向数据的指针)的线程安全的保护。(注:提供对数据的线程安全保护要结合QsharedData来完成)

3.   QExplicitlySharedDataPointer:类似tr1::shared_ptr,提供对数据的线程安全的保护

4.   QtPatternist::AutoPtr (internal class):其实现的功能已经完全被后面的QscopedPointer替代。

5.   QSharedPointer:类似tr1::shared_ptr,也可以提供线程安全保护,与QSharedDataPointer不同的是,线程安全的保护仅限于指针而不是指针所指的对象数据。

6.   QWeakPointer:类似tr1::weak_ptr伴随QsharedPointer产生,可以提供比QPointer更好的性能

7.   QGuard (internal class): 内部类,主要用来替换QPointer而实现更好的性能

8.   QScopedPointer:类似tr1::auto_ptr,可以很方便的实现RAII,而且提供了一个可以定制的deallocator, 因此使用auto_ptr但是苦于没有办法用delete[]的同学们就有福了。

当然你或许会问为什么不用tr1, boost。这个问题也在blog中被回答了,因此Qt是一个跨平台的库,不能保证每个平台上的编译器都实现了tr1,比如Sun Studio compiler on Solaris (Sun Studio 12, with CC 5.10)仍然只支持C++98以前的标准。当然,还有其他的很多原因,比如风格/缺失某些属性之类的。

 

shared_ptr陷阱

条款1:不要把一个原生指针给多个shared_ptr管理
int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error
ptr对象被删除了2次
这种问题比喻成“二龙治水”,在原生指针中也同样可能发生。
 
条款2:不要把this指针给shared_ptr
class Test{
public:
    void Do(){  m_sp =  shared_ptr<Test>(this);  }
private:
    shared_ptr<Test> m_member_sp;
};
 
Test* t = new Test;
shared_ptr<Test> local_sp(t);
p->Do();
 
发生什么事呢,t对象被删除了2次!
t对象给了local_sp管理,然后在m_sp =  shared_ptr<Test>(this)这句里又请了一尊神来管理t。
这就发生了条款1里“二龙治水”错误。
 
条款3:shared_ptr作为被保护的对象的成员时,小心因循环引用造成无法释放资源。
 
对象需要相互协作,对象A需要知道对象B的地址,这样才能给对象B发消息(或调用其方法)。
设计模式中有大量例子,一个对象中有其他对象的指针。现在把原生指针替换为shared_ptr.
 
假设a对象中含有一个shared_ptr<B>指向b对象;假设b对象中含有一个shared_ptr<A>指向a对象
并且a,b对象都是堆中分配的。很轻易就能与他们失去最后联系。
考虑某个shared_ptr<A> local_a;是我们能最后一个看到a对象的共享智能指针,其use_count==2,
因为对象b中持有a的指针。所以当local_a说再见时,local_a只是把a对象的use_count改成1。
同理b对象。然后我们再也看不到a,b的影子了,他们就静静的躺在堆里,成为断线的风筝。
 
解决方案是:Use weak_ptr to "break cycles."(boost文档里写的)或者显示的清理
 
条款4:不要在函数实参里创建shared_ptr
 
function ( shared_ptr<int>(new int), g( ) );  //有缺陷
可能的过程是先new int,然后调g( ),g( )发生异常,shared_ptr<int>没有创建,int内存泄露
 
shared_ptr<int> p(new int());
f(p, g());  //Boost推荐写法
 
条款5:对象内部生成shared_ptr
 
前面说过,不能把this指针直接扔给shared_ptr. 但是没有禁止在对象内部生成自己的shared_ptr
 
//这是Boost的例子改的。
class Y: public boost::enable_shared_from_this<Y>
{
    boost::shared_ptr<Y> GetSelf()
    {
        return shared_from_this();
    }
};
 
原理是这样的。普通的(没有继承enable_shared_from_this)类T的shared_ptr<T> p(new T).
p作为栈对象占8个字节,为了记录(new T)对象的引用计数,p会在堆上分配16个字节以保存
引用计数等“智能信息”。share_ptr没有“嵌入(intrusive)”到T对象,或者说T对象对share_ptr毫不知
 
情。Y对象则不同,Y对象已经被“嵌入”了一些share_ptr相关的信息,目的是为了找到“全局性”的
那16字节的本对象的“智能信息”。
 
原理说完了,就是陷阱
Y y;
boost::shared_ptr<Y> p=  y.GetSelf(); //无知的代码,y根本就不是new出来的
 
Y* y = new Y;
boost::shared_ptr<Y> p=  y->GetSelf(); //似是而非,仍旧程序崩盘。
Boost文档说,在调用shared_from_this()之前,必须存在一个正常途径创建的shared_ptr
 
boost::shared_ptr<Y> spy(new Y)
boost::shared_ptr<Y> p =  spy->GetSelf(); //OK
 
条款6 :处理不是new的对象要小心。
 
int* pi = (int*)malloc(4)
shared_ptr<int> sp( pi ) ; //delete马嘴不对malloc驴头。
 
条款7:多线程对引用计数的影响。
 
如果是轻量级的锁,比如InterLockIncrement等,对程序影响不大
如果是重量级的锁,就要考虑因为share_ptr维护引用计数而造成的上下文切换开销。
1.33版本以后的shared_ptr对引用计数的操作使用的是Lock-Free(类似InterLockIncrement函数族)
的操作,应该效率不错,而且能保证线程安全(库必须保证其安全,程序员都没有干预这些隐藏事物的机会)。
Boost文档说read,write同时对shared_ptr操作时,行为不确定。这是因为shared_ptr本身有两个成员px,pi。
多线程同时对px读写是要出问题的。与一个int的全局变量多线程读写会出问题的原因一样。
 
条款8:对象数组用shared_array
 
int* pint = new int[100];
shared_array<int> p (pint );
 
既然shared_ptr对应着delete;显然需要一个delete[]对应物shared_array
 
条款9:学会用删除器
 
struct Test_Deleter
{   
    void  operator ()( Test* p){   ::free(p);   }
};
Test* t = (Test*)malloc(sizeof(Test));
new (t) Test;
 
shared_ptr<Test> sp( t ,  Test_Deleter() ); //删除器可以改变share_ptr销毁对象行为
 
有了删除器,shared_array无用武之地了。
template<class T>
struct Array_Deleter
{   
    void  operator ()( T*){   delete[] p;   }
};
int* pint = new int[100];
shared_ptr<int> p (pint, Array_Deleter<int>() );
 
条款10:学会用分配器
 
存放引用计数的地方是堆内存,需要16-20字节的开销。
如果大量使用shared_ptr会造成大量内存碎片。
shared_ptr构造函数的第3个参数是分配器,可以解决这个问题。
 
shared_ptr<Test> p( (new Test), Test_Deleter(), Mallocator<Test>() );
注意删除器Test_Deleter是针对Test类的。分配器是针对shared_ptr内部数据的。
 
Mallocator<Test>()是个临时对象(无状态的),符合STL分配器规约。
 
template <typename T> 
class Mallocator { 
    //略。。。。。。
    T * allocate(const size_t n) const {
        return singleton_pool<T,sizeof(T)>::malloc();
    }
    //略。。。。。。
 
Mallocator传入Test,实际分配的类型确是
class boost::detail::sp_counted_impl_pda<class Test *,
                                         struct Test_Deleter,
                                         class Mallocator<class Test> >
这是用typeid(T).name()打印出来的。可能和rebind相关。
 
条款11 weak_ptr在使用前需要检查合法性。
weak_ptr<K> wp;
{
shared_ptr<K>  sp(new K);  //sp.use_count()==1
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
shared_ptr<K> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
shared_ptr<K> sp_null = wp.lock(); //sp_null .use_count()==0;
因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。
得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。
因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。
直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法直到自己
所容纳的那个指针资源的当前状态。
 
条款12 不要new shared_ptr<T>
 
本来shared_ptr就是为了管理指针资源的,不要又引入一个需要管理的指针资源shared_ptr<T>*
 
条款13  尽量不要get
 
class B{...};
class D : public B{ ...};  //继承层次关系
 
shared_ptr<B> sp (new D);    //通过隐式转换,储存D的指针。
B* b = sp.get();             //shared_ptr辛辛苦苦隐藏的原生指针就这么被刨出来了。
D* d = dynamic_cast<D*>(b);  //这是使用get的正当理由吗?
 
正确的做法
shared_ptr<B> spb (new D)  ;
shared_ptr<D> spd = shared_dynamic_cast<D>(spb); //变成子类的指针
shared_ptr在竭尽全力表演的像一个原生指针,原生指针能干的事,它也基本上能干。
 
另一个同get相关的错误
shared_ptr<T> sp(new T);
shared_ptr<T> sp2( sp.get() ) ;//又一个“二龙治水”实例,指针会删2次而错误。
 
条款14 不要memcpy shared_ptr
 
shared_ptr<B> sp1 (new B)  ;
shared_ptr<B> sp2;
memcpy(&sp2,&sp1,sizeof(shared_ptr<B>)); //sp2.use_count()==1
很显然,不是通过正常途径(拷贝构造,赋值运算),引用计数是不会正确增长的。
 
条款15 使用BOOST预定义的宏去改变shared_ptr行为。
 
shared_ptr行为由类似BOOST_SP_DISABLE_THREADS这样的宏控制。需要去学习他们到底是干什么的。
大师Andrei Alexandrescu设计了一种基于模板策略设计模式的智能指针,通过几个模板参数去定制化
智能指针的行为。Boost却不以为然,官方解释是:需要统一的接口,这样利于大规模书写。
smart_ptr<T,OwnershipPolicy,ConversionPolicy,CheckingPolicy,StoragePolicy> sp(new T);
上述接口缺点是外形复杂,看上去像个大花脸。优点是客户程序员可以轻易的定制行为。
 
条款17 构造函数里调用shared_from_this抛例外
 
class Holder:public enable_shared_from_this<Holder>{
public:
    Holder() {
        shared_ptr<Holder> sp = shared_from_this();
        int x = sp.use_count();
    }
};
同前面条款5,不符合enable_shared_from_this使用前提。
 
总结:
学习了一天就总结出10多条条款,长期研究一下恐怕就出现条款100了。为什么还要使用shared_ptr呢?
有很多开源库用shared_ptr,而且shared_ptr具有“传染性”(某网友语:像毒@@品沾上就甩不掉),
抛开它就会有更严重的多龙治水现象。shared_ptr作为原生指针的替代品,能解决一定的内存泄露问题。
实际上初学原生指针时,每个人都遇到过野指针,删两次,忘记删除等问题。学习shared_ptr也会遇到。
shared_ptr的确能改善上述问题,并不能完全解决问题。shared_ptr可能在将来占主流,它最可能号令江湖,
否则一大堆auto_ptr,weak_ptr,原生指针,scoped_ptr共存就把人搞糊涂了。
1
0
分享到:
评论
1 楼 hewusheng 2014-08-15  
嘿嘿,不会C++

相关推荐

    智能指针类型转换.pdf

    其中,`shared_ptr`是C++11引入的一种智能指针,它实现了引用计数机制,当最后一个`shared_ptr`实例被销毁时,其管理的对象也会被删除。然而,在处理多态对象时,指针类型转换是非常常见的需求,以便能够使用基类...

    cvtest1_ME_с++._

    6. **内存管理**:C++允许直接操作内存,包括动态内存分配(new、delete)和智能指针(如unique_ptr、shared_ptr、weak_ptr),以避免内存泄漏和悬挂指针问题。 7. **C++11及以后的新特性**:自C++11以来,C++引入...

    C++11 in Qt源码及PPT

    - 包括右值引用、move语义、智能指针(如std::unique_ptr和std::shared_ptr)、并行算法等,这些都极大地优化了Qt中的资源管理和性能。 9. **测试代码**(enableCPlusPlus11) - 提供的测试代码展示了如何启用C++...

    DLL导出类的三种方法,导出接口类,导出类指针,导出类,不建议使用导出类

    对于需要传递对象实例的情况,可以考虑使用智能指针如`std::unique_ptr`或`std::shared_ptr`来管理内存,以避免内存泄漏。 总的来说,选择适当的DLL导出方式是提高软件设计质量的关键一步,需要根据项目需求和预期...

    print_preview_listbox.zip_c++ 打印_list 打印_preview_print_打印

    这可能涉及到`new`和`delete`操作符,以及智能指针如`std::unique_ptr`或`std::shared_ptr`的使用。 10. **错误处理**: 程序应该包含适当的错误处理机制,如异常处理,以应对可能出现的问题,如文件未找到、...

    C++库大全,教你怎样更好使用C++类库

    3. **智能指针**:C++11引入了shared_ptr、unique_ptr和weak_ptr,它们是自动管理内存的对象,避免了传统指针可能导致的内存泄漏问题。例如,shared_ptr可以用来共享所有权,当最后一个拥有对象的shared_ptr消亡时,...

    c++boost库最新版本

    2. **智能指针**:Boost库中的smart_ptr系列(例如shared_ptr、unique_ptr和weak_ptr)是C++标准库中std::shared_ptr和std::unique_ptr的前身,它们解决了原始指针可能导致的内存泄漏问题,提供了更安全的内存管理...

    C_C++各种开发中文API帮助文档集合

    6. **智能指针**:unique_ptr, shared_ptr, weak_ptr等智能指针类是C++11引入的,自动管理内存,避免了内存泄漏的问题。 7. **文件系统操作**:C++17引入了文件系统库,提供对文件和目录的操作,如创建、删除、读写...

    boost程序库完全开发指南深入c++准标准库第3版-第4章

    首先,章节可能介绍的是Boost智能指针(Smart Pointers),如`shared_ptr`, `unique_ptr`, 和 `weak_ptr`。这些智能指针是C++11之前管理动态内存的解决方案,它们能够自动处理对象的生命周期,避免了内存泄漏的问题...

    boost程序库完全开发指南深入c++准标准库第3版-第8章

    1. Boost智能指针:Boost库提供了几种智能指针类型,如`shared_ptr`、`unique_ptr`和`weak_ptr`,它们在管理对象生命周期方面比C++标准库中的`std::auto_ptr`更加安全和灵活。`shared_ptr`用于共享所有权,`unique_...

    Linux 内核里的“智能指针”

    现代的C/C++类库一般会提供智能指针来作为内存管理的折中方案,比如STL的auto_ptr,Boost的Smart_ptr库,QT的QPointer家族,甚至是基于C语言构建的GTK+也通过引用计数来实现类似的功能。Linux内核是如何解决这个问题...

    电子表格 QT C++ 程序 源代码

    使用智能指针(如std::unique_ptr或std::shared_ptr)可以有效地管理对象生命周期,防止内存泄漏。同时,通过try-catch块捕获可能出现的异常,确保程序在遇到错误时能优雅地处理。 总的来说,创建一个电子表格QT ...

    AutoDeleterTest.zip

    智能指针,如`std::unique_ptr`、`std::shared_ptr`和`std::weak_ptr`,是C++标准库提供的另一种解决内存管理的方式。它们提供了更高级的功能,比如所有权转移和引用计数。然而,`AutoDeleter`的实现可能更简单,...

    c++学习视频资料全集.docx

    * 智能指针:unique_ptr、shared_ptr、weak_ptr等 * move语义:右值引用、std::move、std::forward等 * SFINAE:Substitution Failure Is Not An Error的概念和应用 五、C++开发实践 * Visual C++开发环境:项目...

    八股文C++篇(超级全)-c++八股文.pdf

    - **unique_ptr**: 独占所有权模型,确保资源只被一个智能指针拥有。 - **weak_ptr**: 非拥有模型,与`shared_ptr`配合使用,避免循环引用。 ### 9. Lambda表达式 - **语法**: `[capture](parameters) mutable|...

    我的笔记-qt-c++-c#-后面持续更新.7z

    6. **智能指针**:如unique_ptr、shared_ptr,用于自动管理内存。 7. **C++11及以后的新特性**:如lambda函数、右值引用、auto关键字等。 **C#知识点** C#是微软开发的现代编程语言,主要用于Windows应用、Web服务...

    c++指针大全

    10. **智能指针**:为了解决原始指针可能导致的内存管理问题,C++11引入了智能指针(如`std::unique_ptr`、`std::shared_ptr`),它们自动管理内存,防止资源泄露。 11. **进程间通信**:虽然不是指针直接相关的...

    Beyond the C++Standard Library(boost中文版)

    - `shared_ptr`:支持多个智能指针共享同一对象的所有权,可以实现资源的共享。 - `shared_array`:与`shared_ptr`类似,但用于数组。 - `intrusive_ptr`:与`shared_ptr`类似,但是被管理的对象需要自己实现引用...

    c、c++智能数据架构

    智能指针(如`std::unique_ptr`, `std::shared_ptr`)是C++11引入的,用于自动管理对象生命周期,减少内存泄漏和悬挂指针的风险。 4. **设计模式**:在构建多层次数据架构时,设计模式是指导原则。例如,工厂模式...

Global site tag (gtag.js) - Google Analytics