C++ --- C++智能指针

Posted Overboom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ --- C++智能指针相关的知识,希望对你有一定的参考价值。

1. 为什么需要引入智能指针

智能指针是一个类,当超出了类的实例对象作用域时,会自动调用对象的析构函数来释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都是由程序员自己管理。但是使用普通指针容易造成堆内存泄露(忘记释放),二次释放等内存泄露问题,使用智能指针可以更好的管理堆内存。

2. C++11智能指针介绍

C++里面的四个智能指针包含在头文件中: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。

2.1 std::shared_ptr的使用

1> 类似vector的作用,在使用智能指针时需要给它指定所指向的类型,指针的初始化可以通过构造函数、std::make_shared、reset()实现;
2> 对于一个未初始化的指针,调用reset()可以将其初始化,对于一个已经初始化的智能指针,调用reset()释放其所指对象;
3> 不能将指针直接赋值给一个智能指针,一个是指针,一个是类;
4> 调用get()函数获取原始指针

#include <iostream>
#include <memory>

int main()
{
        std::shared_ptr<int> ptr1(new int(1));  //ptr1指向int类型
        std::shared_ptr<int> ptr2(ptr1);        //用ptr初始ptr2,此时ptr1指向的对象引用计数是2
        std::cout << "ptr1 引用计数:" << ptr1.use_count() << std::endl;
        std::cout << "ptr2 引用计数:" << ptr2.use_count() << std::endl;

        int a = 20;
        int *pint = &a;
        pint = ptr1.get();
        std::cout << *pint << std::endl;

        std::shared_ptr<std::string> ptr3 = std::make_shared<std::string>("Hello World");                           //ptr3指向string类型
        std::cout << "ptr3 is : " << *ptr3 << std::endl;
        std::cout << "ptr3 引用计数:" << ptr3.use_count() << std::endl;

        std::shared_ptr<std::string> ptr4;     //ptr4指向string类型
        ptr4.reset(new std::string("reset init"));
        std::cout << "ptr4 is : " << *ptr4<< std::endl;
        std::cout << "ptr4 引用计数:" << ptr4.use_count() << std::endl;

        ptr4 = ptr3;
        std::cout << "ptr3 引用计数:" << ptr3.use_count() << std::endl;
        std::cout << "ptr4 引用计数:" << ptr4.use_count() << std::endl;

        ptr4.reset();
        std::cout << "ptr3 引用计数:" << ptr3.use_count() << std::endl;
        std::cout << "ptr4 引用计数:" << ptr4.use_count() << std::endl;


        //std::shared_ptr<int> ptr5 = new int(1);               //error

        return 0;
}

编译输出:
5> 避免循环引用

#include <iostream>
#include <memory>

class B;
class A {
public:
    ~A()
    {
        std::cout << "A is des" << std::endl;
    }
    std::shared_ptr<B> aptr;
};

class B {
public:
    ~B()
    {
        std::cout << "B is des" << std::endl;
    }
    std::shared_ptr<A> bptr;
};

void test()
{
    std::shared_ptr<A> ptr1(new A());
    std::shared_ptr<B> ptr2(new B());

//    ptr1->aptr = ptr2;  //循环引用
//    ptr2->bptr = ptr1;

    std::cout << "A use count is " << ptr1.use_count() << std::endl;
    std::cout << "B use count is " << ptr2.use_count() << std::endl;

}

int main()
{
    test();

    return 0;
}

如果将test()函数中的两行注释打开,就是造成循坏引用,类A与类B的西沟函数不会被调用,A和B的引用计数都是2,离开作用于变成1,永远不会被析构, 出现内存泄露。

2.2 std::unique_ptr的使用

1> unique_ptr是独占型的指针,某个时刻只能有一个std::unique_ptr指向一个给定对象。当std::unique_ptr被销毁的时候,它所指向的对象也会被销毁,不像std::shared_ptr共享所指向的对象;
2> 在unique_ptr的声明周期内,可以改变智能指针的指向对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

#include <iostream>
#include <memory>

int main() {
    {
        std::unique_ptr<int> uptr(new int(10));  //绑定动态对象
        //std::unique_ptr<int> uptr2 = uptr;  //不能賦值
        //std::unique_ptr<int> uptr2(uptr);  //不能拷貝
        std::unique_ptr<int> uptr2 = std::move(uptr); //转换所有权
        uptr2.release(); //释放所有权
    }
    //超過uptr的作用域,內存釋放
}

2.3 std::weak_ptr的使用

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

#include <iostream>
#include <memory>

int main() {
    {
        std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
        std::cout << sh_ptr.use_count() << std::endl;

        std::weak_ptr<int> wp(sh_ptr);
        std::cout << wp.use_count() << std::endl;

        if(!wp.expired()){
            std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
            *sh_ptr = 100;
            std::cout << wp.use_count() << std::endl;
        }
    }
    //delete memory
}

2.4 循环引用

考虑一个简单的对象建模——家长与子女:a Parent has a Child, a Child know his/her Parent。如果使用原始指针作为成员,Child和Parent由谁释放?那么如何保证指针的有效性?如何防止出现空悬指针?这些问题是C++面向对象编程麻烦的问题,现在可以借助smart pointer把对象语义(pointer)转变为值(value)语义,shared_ptr轻松解决生命周期的问题,不必担心空悬指针。但是这个模型存在循环引用的问题,注意其中一个指针应该为weak_ptr。

原始指针的做法,容易出错

#include <iostream>
#include <memory>

class Child;
class Parent;

class Parent {
private:
    Child* myChild;
public:
    void setChild(Child* ch) {
        this->myChild = ch;
    }

    void doSomething() {
        if (this->myChild) {

        }
    }

    ~Parent() {
        delete myChild;
    }
};

class Child {
private:
    Parent* myParent;
public:
    void setPartent(Parent* p) {
        this->myParent = p;
    }
    void doSomething() {
        if (this->myParent) {

        }
    }
    ~Child() {
        delete myParent;
    }
};

int main() {
    {
        Parent* p = new Parent;
        Child* c =  new Child;
        p->setChild(c);
        c->setPartent(p);
        delete c;  //only delete one
    }
    return 0;
}

循坏引用内存泄露的问题

#include <iostream>
#include <memory>

class Child;
class Parent;

class Parent {
private:
    std::shared_ptr<Child> ChildPtr;
public:
    void setChild(std::shared_ptr<Child> child) {
        this->ChildPtr = child;
    }

    void doSomething() {
        if (this->ChildPtr.use_count()) {

        }
    }

    ~Parent() {
    }
};

class Child {
private:
    std::shared_ptr<Parent> ParentPtr;
public:
    void setPartent(std::shared_ptr<Parent> parent) {
        this->ParentPtr = parent;
    }
    void doSomething() {
        if (this->ParentPtr.use_count()) {

        }
    }
    ~Child() {
    }
};

int main() {
    std::weak_ptr<Parent> wpp;
    std::weak_ptr<Child> wpc;
    {
        std::shared_ptr<Parent> p(new Parent);
        std::shared_ptr<Child> c(new Child);
        p->setChild(c);
        c->setPartent(p);
        wpp = p;
        wpc = c;
        std::cout << p.use_count() << std::endl; // 2
        std::cout << c.use_count() << std::endl; // 2
    }
    std::cout << wpp.use_count() << std::endl;  // 1
    std::cout << wpc.use_count() << std::endl;  // 1
    return 0;
}

正确的做法

#include <iostream>
#include <memory>

class Child;
class Parent;

class Parent {
private:
    //std::shared_ptr<Child> ChildPtr;
    std::weak_ptr<Child> ChildPtr;
public:
    void setChild(std::shared_ptr<Child> child) {
        this->ChildPtr = child;
    }

    void doSomething() {
        //new shared_ptr
        if (this->ChildPtr.lock()) {

        }
    }

    ~Parent() {
    }
};

class Child {
private:
    std::shared_ptr<Parent> ParentPtr;
public:
    void setPartent(std::shared_ptr<Parent> parent) {
        this->ParentPtr = parent;
    }
    void doSomething() {
        if (this->ParentPtr.use_count()) {

        }
    }
    ~Child() {
    }
};

int main() {
    std::weak_ptr<Parent> wpp;
    std::weak_ptr<Child> wpc;
    {
        std::shared_ptr<Parent> p(new Parent);
        std::shared_ptr<Child> c(new Child);
        p->setChild(c);
        c->setPartent(p);
        wpp = p;
        wpc = c;
        std::cout << p.use_count() << std::endl; // 2
        std::cout << c.use_count() << std::endl; // 1
    }
    std::cout << wpp.use_count() << std::endl;  // 0
    std::cout << wpc.use_count() << std::endl;  // 0
    return 0;
}

参考链接:
https://www.cnblogs.com/wxquare/p/4759020.html
https://zhuanlan.zhihu.com/p/157677773
https://www.cnblogs.com/WindSun/p/11444429.html

以上是关于C++ --- C++智能指针的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针(3.30)

指向外部托管(例如:Python)资源的 C++ 智能指针?

c++智能指针介绍_再补充

C++中的智能指针

C++ --- C++智能指针

C++智能指针详解:智能指针的引入