• C++中的四个智能指针


    只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。智能指针定义在memory头文件中。

    1. auto_ptr(C++11已经舍弃)

    由new expression获得的对象,在auto_ptr对象销毁时,他所管理的对象也会自动被delete掉。

    auto_ptr<string> p1(new string("This is a string"));
    auto_ptr<string> p2;
    p2 = p1;
    

    上述语句中,如果p1和p2是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次。
    要避免这种问题,方法有多种:

    1. 定义赋值运算符,使之执行深拷贝。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
    2. 建立所有权概念。对于特定的对象,只能有一个指针可拥有,这样所拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略。
    3. 跟踪引用对象的智能指针数。这称为引用计数。例如:赋值时,计数加1,指针过期时,计数减1。当减为0时才调用delete。这就是sheared_ptr采用的策略。

    为什么要弃用auto_ptr?

    #include<memory>
    #include<iostream>
    using namespace std;
    int main()
    {
        auto_ptr<string> p1(new string("This is a string"));
        auto_ptr<string> p2 = p1;  //p1将所有权转让给p2,此时p1不再引用该字符串从而变成空指针。
        cout << *p1 << endl;       //报错,此时p1已经没有所指向的内存的所有权。
        cout << *p2 << endl;
    }
    
    1. 使用shared_ptr时运行正常,因为shared_ptr采用引用计数,p1和p2都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。
    2. 使用unique_ptr时编译出错,与auto_ptr不同的是,使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译时出现错误。

    舍弃auto_ptr的原因:避免因潜在的内存问题导致程序崩溃

    2. unique_ptr(替换auto_ptr)

    unique_ptr比auto_ptr更加安全,因为auto_ptr有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr禁止了拷贝语义,但提供了移动语义,即可以使用move()进行控制权限的转移。

    unique_ptr<string> p1(new string("This is a string"));
    unique_ptr<string> p2(p1);      //编译出错,已禁止拷贝
    unique_ptr<string> p3 = p1;     //编译出错,已禁止拷贝
    unique_ptr<string> p4 = std :: move(p1);  //控制权限转移
    
    auto_ptr<string> p1(new string("This is a string"));
    auto_ptr<string> p2(p1);  //编译通过,运行出错
    auto_ptr<string> p3 = p1; //编译通过,运行出错
    

    如果unique_ptr是个临时右值,编译器允许拷贝语义。

    unique_ptr<string> demo(string *s)
    {
        unique_ptr<string> temp(new string(s));
        return temp;
    }
    
    unique_ptr<string> ps;
    ps = demo("This is a string");
    

    demo()返回一个临时unique_ptr,然后ps接管了临时对象所管理的资源,而返回时临时的unique_ptr被销毁,也就是说没有机会使用unique_ptr来访问无效的数据。相对于auto_ptr任何情况下都允许拷贝语义,这正是unique_ptr更加灵活的地方。

    扩展auto_ptr不能完成的功能:

    1. unique_ptr可放在容器中,弥补了auto_ptr不能作为容器元素的缺点。
    2. 管理动态数组
    3. 自定义资源删除操作。

    3. shared_ptr(引用计数型智能指针)

    类似vector,智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息---指针可以指向的类型。

    shared_ptr<string> p1;
    

    默认初始化的智能指针中保存着一个空指针。
    智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。

    make_shared函数
    最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

    shared_ptr<int> p1 = make_shared<int>(42);
    shared_ptr<string> p2 = make_shared<string>(10, '9');
    auto p3 = make_shared<vector<string>>();
    

    shared_ptr的拷贝和赋值

    1. 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。
    2. 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

    shared_ptr自动销毁所管理的对象

    1. 它是通过另一个特殊的成员函数---析构函数(destructor)完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制该类型的对象销毁时做什么操作。
    2. 析构函数一般用来释放对象所分配的资源。
    3. shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

    shared_ptr还会释放相关联的内存

    1. shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保使用erase删除哪些不再需要的shared_ptr元素。

    使用动态生存期的资源的类

    1. 程序不知道自己需要使用多少个对象(容器类)
    2. 程序不知道所需对象的准确类型
    3. 程序需要在多个对象间共享数据(如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面的销毁底层数据。)

    4. weak_ptr(辅助shared_ptr)

    1. weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
    2. 创建一个weak_ptr时,要用一个shared_ptr来初始化它
    3. 不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr.

    5.shared_ptr循环引用

    #include<iostream>
    #include<memory> 
    using namespace std;
    
    class ListNode{
    public:
    	int m_value;
    	shared_ptr<ListNode> prev;
    	shared_ptr<ListNode> next;
    	//构造函数
    	ListNode(int value):m_value(value){
    		cout << "constructor called!" <<endl;
    	}
    	//析构函数
    	~ListNode(){
    		cout << "destructor called!" <<endl;
    	} 
    };
    
    void test(){
    	shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
    	shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
    	cout << sp1.use_count() << endl;
    	cout << sp2.use_count() << endl;
    	sp1 -> next = sp2;
    	sp2 -> prev = sp1;
    	cout << sp1.use_count() << endl;
    	cout << sp2.use_count() << endl;
    }
    
    int main(){
    	test();
    	return 0;
    } 
    //运行结果:
    constructor called!
    constructor called!
    1
    1
    2
    2
    

    构造的sp1和sp2在出它们的作用域(即test())时,都没有被析构,从而造成了内存泄漏。

    6.weak_ptr是如何解决循环引用的?

    #include<iostream>
    #include<memory> 
    using namespace std;
    
    class ListNode{
    public:
    	int m_value;
    	weak_ptr<ListNode> prev;
    	weak_ptr<ListNode> next;
    	//构造函数
    	ListNode(int value):m_value(value){
    		cout << "constructor called!" <<endl;
    	}
    	//析构函数
    	~ListNode(){
    		cout << "destructor called!" <<endl;
    	} 
    };
    
    void test(){
    	shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
    	shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
    	cout << sp1.use_count() << endl;
    	cout << sp2.use_count() << endl;
    	sp1 -> next = sp2;
    	sp2 -> prev = sp1;
    	cout << sp1.use_count() << endl;
    	cout << sp2.use_count() << endl;
    }
    
    int main(){
    	test();
    	return 0;
    } 
    //运行结果:
    constructor called!
    constructor called!
    1
    1
    1
    1
    destructor called!
    destructor called!
    

    弱引用不修改对象的引用计数;弱引用能检测对象是否已经被释放。

    只要把循环引用的一方使用弱引用,即可解除循环引用。

  • 相关阅读:
    脱离Table组件的render苦海
    激活码
    Unittest
    IO多路复用-EPOOL 详细
    PIL-Image、ImageDraw图像标记
    Mysql专场
    xshell命令
    并发编程
    Mysql高级用法
    视图函数中进行sql查询,防止sql注入
  • 原文地址:https://www.cnblogs.com/xiaobaizzz/p/12167393.html
Copyright © 2020-2023  润新知