精华内容
下载资源
问答
  • 共享指针

    千次阅读 2018-09-18 13:22:26
    共享指针使用实例: https://mp.csdn.net/postedit 原址:http://www.cnblogs.com/TenosDoIt/p/3456704.html 本文介绍c++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11...

     

     共享指针使用实例:

    https://mp.csdn.net/postedit

    原址:http://www.cnblogs.com/TenosDoIt/p/3456704.html

    本文介绍c++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用。

    为什么要使用智能指针:我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。下面我们逐个介绍。

    auto_ptr (官方文档

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    class Test

    {

    public:

        Test(string s)

        {

            str = s;

           cout<<"Test creat\n";

        }

        ~Test()

        {

            cout<<"Test delete:"<<str<<endl;

        }

        string& getStr()

        {

            return str;

        }

        void setStr(string s)

        {

            str = s;

        }

        void print()

        {

            cout<<str<<endl;

        }

    private:

        string str;

    };

     

     

    int main()

    {

        auto_ptr<Test> ptest(new Test("123"));

        ptest->setStr("hello ");

        ptest->print();

        ptest.get()->print();

        ptest->getStr() += "world !";

        (*ptest).print();

        ptest.reset(new Test("123"));

        ptest->print();

        return 0;

    }

    运行结果如下

    image

    如上面的代码:智能指针可以像类的原始指针一样访问类的public成员,成员函数get()返回一个原始的指针,成员函数reset()重新绑定指向的对象,而原来的对象则会被释放。注意我们访问auto_ptr的成员函数时用的是“.”,访问指向对象的成员时用的是“->”。我们也可用声明一个空智能指针auto_ptr<Test>ptest();

    当我们对智能指针进行赋值时,如ptest2 = ptest,ptest2会接管ptest原来的内存管理权,ptest会变为空指针,如果ptest2原来不为空,则它会释放原来的资源,基于这个原因,应该避免把auto_ptr放到容器中,因为算法对容器操作时,很难避免STL内部对容器实现了赋值传递操作,这样会使容器中很多元素被置为NULL。判断一个智能指针是否为空不能使用if(ptest == NULL),应该使用if(ptest.get() == NULL),如下代码                                                           本文地址

    1

    2

    3

    4

    5

    6

    7

    8

    9

    int main()

    {

        auto_ptr<Test> ptest(new Test("123"));

        auto_ptr<Test> ptest2(new Test("456"));

        ptest2 = ptest;

        ptest2->print();

        if(ptest.get() == NULL)cout<<"ptest = NULL\n";

        return 0;

    }

    image

    还有一个值得我们注意的成员函数是release,这个函数只是把智能指针赋值为空,但是它原来指向的内存并没有被释放,相当于它只是释放了对资源的所有权,从下面的代码执行结果可以看出,析构函数没有被调用。

    1

    2

    3

    4

    5

    6

    int main()

    {

        auto_ptr<Test> ptest(new Test("123"));

        ptest.release();

        return 0;

    }

    image

    那么当我们想要在中途释放资源,而不是等到智能指针被析构时才释放,我们可以使用ptest.reset(); 语句。


    unique_ptr (官方文档) 

     

     

    unique_ptr,是用于取代c++98的auto_ptr的产物,在c++98的时候还没有移动语义(move semantics)的支持,因此对于auto_ptr的控制权转移的实现没有核心元素的支持,但是还是实现了auto_ptr的移动语义,这样带来的一些问题是拷贝构造函数和复制操作重载函数不够完美,具体体现就是把auto_ptr作为函数参数,传进去的时候控制权转移,转移到函数参数,当函数返回的时候并没有一个控制权移交的过程,所以过了函数调用则原先的auto_ptr已经失效了.在c++11当中有了移动语义,使用move()把unique_ptr传入函数,这样你就知道原先的unique_ptr已经失效了.移动语义本身就说明了这样的问题,比较坑爹的是标准描述是说对于move之后使用原来的内容是未定义行为,并非抛出异常,所以还是要靠人肉遵守游戏规则.再一个,auto_ptr不支持传入deleter,所以只能支持单对象(delete object),而unique_ptr对数组类型有偏特化重载,并且还做了相应的优化,比如用[]访问相应元素等.

    unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权,包括:

    1、拥有它指向的对象

    2、无法进行复制构造,无法进行复制赋值操作。即无法使两个unique_ptr指向同一个对象。但是可以进行移动构造和移动赋值操作

    3、保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象

    unique_ptr 可以实现如下功能:

    1、为动态申请的内存提供异常安全

    2、讲动态申请的内存所有权传递给某函数

    3、从某个函数返回动态申请内存的所有权

    4、在容器中保存指针

    5、auto_ptr 应该具有的功能

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    unique_ptr<Test> fun()

    {

        return unique_ptr<Test>(new Test("789"));

    }

    int main()

    {

        unique_ptr<Test> ptest(new Test("123"));

        unique_ptr<Test> ptest2(new Test("456"));

        ptest->print();

        ptest2 = std::move(ptest);//不能直接ptest2 = ptest

        if(ptest == NULL)cout<<"ptest = NULL\n";

        Test* p = ptest2.release();

        p->print();

        ptest.reset(p);

        ptest->print();

        ptest2 = fun(); //这里可以用=,因为使用了移动构造函数

        ptest2->print();

        return 0;

    }

    image

    unique_ptr 和 auto_ptr用法很相似,不过不能使用两个智能指针赋值操作,应该使用std::move; 而且它可以直接用if(ptest == NULL)来判断是否空指针;release、get、reset等用法也和auto_ptr一致,使用函数的返回值赋值时,可以直接使用=, 这里使用c++11 的移动语义特性。另外注意的是当把它当做参数传递给函数时(使用值传递,应用传递时不用这样),传实参时也要使用std::move,比如foo(std::move(ptest))。它还增加了一个成员函数swap用于交换两个智能指针的值


    share_ptr (官方文档)

    从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。出了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。具体的成员函数解释可以参考 here

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    int main()

    {

        shared_ptr<Test> ptest(new Test("123"));

        shared_ptr<Test> ptest2(new Test("456"));

        cout<<ptest2->getStr()<<endl;

        cout<<ptest2.use_count()<<endl;

        ptest = ptest2;//"456"引用次数加1,“123”销毁

        ptest->print();

        cout<<ptest2.use_count()<<endl;//2

        cout<<ptest.use_count()<<endl;//2

        ptest.reset();

        ptest2.reset();//此时“456”销毁

        cout<<"done !\n";

        return 0;

    }

    image


     

     

     

     

     

    weak_ptr(官方文档)

    weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    class B;

    class A

    {

    public:

        shared_ptr<B> pb_;

        ~A()

        {

            cout<<"A delete\n";

        }

    };

    class B

    {

    public:

        shared_ptr<A> pa_;

        ~B()

        {

            cout<<"B delete\n";

        }

    };

     

    void fun()

    {

        shared_ptr<B> pb(new B());

        shared_ptr<A> pa(new A());

        pb->pa_ = pa;

        pa->pb_ = pb;

        cout<<pb.use_count()<<endl;

        cout<<pa.use_count()<<endl;

    }

     

    int main()

    {

        fun();

        return 0;

    }

    image

    可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr<B> pb_; 改为weak_ptr<B> pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。

    image

    注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr<B> p = pa->pb_.lock();    p->print();

    展开全文
  • 目录 简介 示例 代码 输出 创建 定义删除器 示例 处理数组 销毁其他资源 接口列表 ...共享指针shared_ptr是具有共享所有权语义的智能指针。 每当共享指针shared_ptr的最后一个所有者...

    目录

    简介

    示例

    代码

    输出

    创建

    定义删除器

    示例

    处理数组

    销毁其他资源

    接口列表

    隐式指针转换

    比较运算符

    获取删除器

    别名构造函数

    make_shared()和allocate_shared()

    强制类型转换

    线程安全接口

    使用错误


    简介

    共享指针shared_ptr是具有共享所有权语义的智能指针。 每当共享指针shared_ptr的最后一个所有者被销毁时,关联对象都将被删除(或关联资源被清除)。

    模板类shared_ptr<>被模板化为初始指针所指向的对象的类型(类型参数可能为void):

    namespace std {
        template <typename T>
        class shared_ptr
        {
            public:
                typedef T element_type;
                ...
        };
    }

    示例

    代码

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    
    using namespace std;
    
    int main()
    {
        //新建两个共享型智能指针
        shared_ptr<string> pTom(new string("tom"));
        shared_ptr<string> pJerry(new string("jerry"));
        cout << "Start: " << *pTom << "\t" << *pJerry << endl;
    
        //首字母大写
        (*pTom)[0] = 'T';
        pJerry->replace(0,1,"J");
        cout << "After capitalization: " << *pTom << "\t" << *pJerry << endl;
        
        //多次加入容器
        vector<shared_ptr<string>> whoCleanRoom;
        whoCleanRoom.push_back(pJerry);
        whoCleanRoom.push_back(pJerry);
        whoCleanRoom.push_back(pTom);
        whoCleanRoom.push_back(pJerry);
        whoCleanRoom.push_back(pTom);
    
        //打印所有元素
        cout << "whoCleanRoom: ";
        for (auto ptr = whoCleanRoom.cbegin(); ptr != whoCleanRoom.cend(); ++ptr) {
            cout << **ptr << "  ";
        }
        cout << endl;
    
        //设置新名字
        *pTom = "Tomy";
    
        //打印所有元素
        cout << "whoCleanRoom: ";
        for (auto ptr = whoCleanRoom.cbegin(); ptr != whoCleanRoom.cend(); ++ptr) {
            cout << **ptr << "  ";
        }
        cout << endl;
        
        //打印内部信息
        cout << "use_count: " << whoCleanRoom[0].use_count() << endl;
    }

    输出

    Start: tom	jerry
    After capitalization: Tom	Jerry
    whoCleanRoom: Jerry  Jerry  Tom  Jerry  Tom  
    whoCleanRoom: Jerry  Jerry  Tomy  Jerry  Tomy  
    use_count: 4

    创建

    方式一:shared_ptr<string> pTom{new string("tom")};
    方式二:shared_ptr<string> pTom;
           pTom.reset(new string("tom"));
    方式三:shared_ptr<string> pTom = make_shared<string>("tom");

    相对于方式一和方式二,方式三更快,更安全,因为它只进行了一次内存分配而不是两次(一次为对象分配内存,另一次为共享指针的控制块分配内存)。

    定义删除器

    当要删除资源时,会调用删除器。

    示例

    shared_ptr<string> pTom(new string("tom"),
                            [](string* p) {
                                cout << "delete " << *p << endl;
                                delete p;
                            });

    处理数组

    请注意,共享指针shared_ptr提供的默认删除程序将调用delete,而不是delete []。 这意味着仅当共享指针拥有使用new创建的单个对象时,默认删除器才适用。不幸的是,可以为数组创建一个共享指针shared_ptr,但这是错误的:

    std::shared_ptr<char> ptr(new char[20]); //错误,但能编译通过

    因此,如果使用new []创建对象数组,则必须自定义删除器。 你可以通过传递函数,函数对象或lambda来做到这一点,后者对传递的普通指针调用delete []。 例如:

    std::shared_ptr<char> ptr(new char[20],
                           [](char* p) {
                               delete[] p;
                           }
                          );

    也可以使用default_delete作删除器,因为它使用delete []。使用代码如下:

    std::shared_ptr<char> p(new char[20],
                           std::default_delete<char[]>());

    注意,共享指针shared_ptr不提供运算符[]。

    销毁其他资源

    如果共享指针shared_ptr的清除动作不只是删除内存,则必须自定义删除器。 

    示例一:假设要确保在删除对临时文件的最后一个引用时将其删除,可以使用如下代码:

    #include <string>
    #include <fstream>
    #include <memory>
    #include <cstdio>
    #include <iostream>
    
    using std::cout;
    using std::endl;
    
    class FileDeleter
    {
      public:
        FileDeleter (const std::string& sFileName)
         : m_sFileName(sFileName) {
        }
    
        void operator () (std::ofstream* pOfs) {
            delete pOfs;                     //关闭文件
            std::remove(m_sFileName.c_str()); //删除文件
            cout << "Delete file -- " << m_sFileName << endl;
        }
    
      private:
        std::string m_sFileName;
    };
    
    int main()
    {
        const std::string sFileName = "TempFile.txt";
        std::shared_ptr<std::ofstream> fp(new std::ofstream(sFileName),
                                          FileDeleter(sFileName));
        cout << "Program exit" << endl;
    }

    程序输出:

    Program exit
    Delete file -- TempFile.txt

    以上代码使用新创建的输出文件初始化了共享指针shared_ptr。 传递的FileDeleter可确保使用此共享指针shared_ptr的最后一个副本失去此输出流的所有权时,使用<cstdio>中提供的标准C函数remove()关闭并删除此文件。 因为remove()需要文件名,所以我们将文件名作为参数传递给FileDeleter的构造函数。

    第二个示例演示了如何使用共享指针shared_ptr处理共享内存:

    #include <memory>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <cstring>
    #include <cerrno>
    #include <string>
    #include <iostream>
    
    using std::string;
    
    class SharedMemoryDetacher
    {
        public:
            SharedMemoryDetacher(const string & sFileName)
                : m_sFilePath(sFileName) {}
    
            void operator () (long *) {
                std::cout << "unlink " << m_sFilePath << std::endl;
                if (shm_unlink(m_sFilePath.data()) != 0) {
                    std::cerr << "OOPS: shm_unlink() failed" << std::endl;
                }
            }
    
        private:
            const string & m_sFilePath;
    };
    
    std::shared_ptr<long> getSharedIntMemory (int num, const string & sFilePath)
    {
        void* mem;
        int shmfd = shm_open(sFilePath.data(), O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
        if (shmfd < 0) {
            throw std::string(strerror(errno));
        }
        if (ftruncate(shmfd, num*sizeof(long)) == -1) {
            throw std::string(strerror(errno));
        }
        mem = mmap(nullptr, num*sizeof(long), PROT_READ | PROT_WRITE,
                   MAP_SHARED, shmfd, 0);
        if (mem == MAP_FAILED) {
            throw std::string(strerror(errno));
        }
        return std::shared_ptr<long>(static_cast<long*>(mem),
                                     SharedMemoryDetacher(sFilePath));
    }
    
    int main()
    {
        //获取并连接共享内存
        const int C_INT_NUM = 10;
        const string C_STR_FILE_PATH = "shmTmp";
        std::shared_ptr<long> smp(getSharedIntMemory(C_INT_NUM, C_STR_FILE_PATH));
        
        //初始化共享内存
        for (int i=0; i<C_INT_NUM; ++i) {
            smp.get()[i] = i*36;
        }
    
        //其他地方处理共享内存
        //...
        std::cout << "<return>" << std::endl;
        std::cin.get(); 
    
        //释放共享内存
        smp.reset();
        //...
    }

    首先,定义一个删除程序SharedMemoryDetacher来分离共享内存。 删除程序释放共享内存,该共享内存由getSharedIntMemory()获取并附加。 为了确保在最后一次使用共享内存时删除器被调用,当getSharedIntMemory()为连接的内存创建共享指针shared_ptr时,将传递该删除器:

    return std::shared_ptr<long>(static_cast<long*>(mem),
                                 SharedMemoryDetacher()); // calls shmdt()

    也可以在此处使用lambda(省略前缀std::):

    return shared_ptr<long>(static_cast<long*>(mem),
                           [](long* p) {
                               cout << "unlink shmTemp" << endl;
                               if (shm_unlink("/shmTemp") != 0) {
                                   cerr << "shm_unlink()失败"
                                        << endl;
                               }
                            });

    由于不允许删除器抛出异常,因此仅在此处将错误消息写入std::cerr。

    因为shm_unlink()的签名已经适合作为删除器,所以如果你不想检查其返回值,甚至可以直接使用shm_unlink()作为删除器:

    return std::shared_ptr<long>(static_cast<long*>(mem),
                                 shm_unlink);

    请注意,共享指针shared_ptr仅提供运算符*和->,没有提供指针算术和运算符[]。 因此,要访问内存,必须使用get(),它会返回由共享指针shared_ptr封装的内部指针,以提供完整的指针语义:

    smp.get()[i] = i*36;

    get()提供了另外一种调用方法:

    (&*smp)[i] = i*36;

    对于这两个示例,另一种可能的实现技术可能比这更简洁:只需创建一个新类,构造函数执行初始工作,而析构函数执行清除。 然后,可以只使用共享指针shared_ptr来管理用new创建的此类的对象。 这样做的好处是可以定义一个更直观的接口,例如代表共享内存的对象的运算符[],但应该仔细考虑复制和赋值操作。

    接口列表

    下表列出了为共享指针shared_ptr提供的所有操作。

    操作结果
    shared_ptr<T> sp默认构造函数;使用默认删除器(调用delete)创建一个空的共享指针
    shared_ptr<T> sp(ptr)使用默认删除器(调用delete)创建一个拥有*ptr的共享指针
    shared_ptr<T> sp(ptr,del) 使用del作为删除器创建拥有*ptr的共享指针
    shared_ptr<T> sp(ptr, del, ac)使用del作为删除器并使用ac作为分配器创建一个拥有*ptr的共享指针
    shared_ptr<T> sp(nullptr)使用默认删除器(调用delete)创建空的共享指针
    shared_ptr<T> sp(nullptr, del)使用del作为删除器创建一个空的共享指针
    shared_ptr<T> sp(nullptr, del, ac)使用del作为删除器和ac作为分配器创建一个空的共享指针
    shared_ptr<T> sp(sp2)创建与sp2共享所有权的共享指针
    shared_ptr<T> sp(move(sp2))创建一个共享指针,该共享指针拥有先前由sp2拥有的指针(sp2之后为空)
    shared_ptr<T> sp(sp2, ptr)别名构造函数;创建一个共享指针,共享sp2的所有权,但引用*ptr
    shared_ptr<T> sp(wp)从弱指针wp创建共享指针
    shared_ptr<T> sp(move(up))从unique_ptr创建共享指针
    shared_ptr<T> sp(move(ap))从auto_ptr创建共享指针
    sp.~shared_ptr()析构函数;如果sp拥有对象,则调用deleter
    sp = sp2赋值(sp之后与sp2共享所有权,放弃先前拥有的对象的所有权)
    sp = move(sp2)移动赋值(sp2将所有权转移到sp)
    sp = move(up)使用unique_ptr进行移动赋值(up将所有权转让给sp)
    sp = move(ap)使用auto_ptr进行移动赋值(ap将所有权转让给sp)
    sp1.swap(sp2)交换sp1和sp2的指针和删除器
    swap(sp1, sp2)交换sp1和sp2的指针和删除器
    sp.reset()放弃所有权并将共享指针重新初始化为空
    sp.reset(ptr)放弃所有权并使用默认删除器(称为delete)重新初始化共享指针,拥有*ptr
    sp.reset(ptr, del)放弃所有权并使用del作为删除器重新初始化共享指针,拥有* ptr
    sp.reset(ptr, del, ac)放弃所有权并重新初始化共享指针,拥有* ptr,使用del作为删除器,使用ac作为分配器
    make_shared(...)为通过传递的参数初始化的新对象创建共享指针
    allocate_shared(ac, ...)使用分配器ac为由传递的参数初始化的新对象创建共享指针
    sp.get()返回存储的指针(通常是拥有对象的地址,如果没有则返回nullptr)
    *sp返回拥有的对象(如果没有则为未定义的行为)
    sp->...提供对拥有对象的成员访问权限(如果没有,则行为未定义)
    sp.use_count()返回共享所有者(包括sp)的数目;如果共享指针为空,则返回0
    sp.unique()返回sp是否是唯一所有者(等效于sp.use_count()== 1,但可能更快)
    if (sp)运算符bool();返回sp是否为空
    sp1 == sp2对存储的指针调用==(存储的指针可以为nullptr)
    sp1 != sp2对存储的指针调用!=(存储的指针可以为nullptr)
    sp1 < sp2对存储的指针调用<(存储的指针可以为nullptr)
    sp1 <= sp2对存储的指针调用<=(存储的指针可以为nullptr)
    sp1 > sp2对存储的指针调用>(存储的指针可以为nullptr)
    sp1 >= sp2对存储的指针调用>=(存储的指针可以为nullptr)
    static_pointer_cast(sp)sp的static_cast<>语义
    dynamic_pointer_cast(sp)sp的dynamic_cast<>语义
    const_pointer_cast(sp)sp的const_cast<>语义
    get_deleter(sp)返回删除器的地址(如果有),否则返回nullptr
    strm << sp调用原始指针的输出运算符(等于strm << sp.get())
    sp.owner_before(sp2)提供严格的弱排序和另一个共享指针
    sp.owner_before(wp)通过弱指针提供严格的弱排序

    每当所有权转移到已经拥有另一个对象的共享指针时,如果该共享指针是最后一个所有者,则将调用先前拥有对象的deleter。如果共享指针通过分配新值或调用reset()获得新值,则同样适用。

    空的共享指针shared_ptr不共享对象的所有权,因此use_count()函数返回0。但由于一个特殊的构造函数,共享指针仍可能引用一个对象。

    隐式指针转换

    如果存在隐式指针转换,则共享指针可能使用不同的对象类型。因此,构造函数、赋值运算符和reset()是成员模板,而比较运算符则针对不同类型进行模板化。

    比较运算符

    所有比较运算符都会调用共享指针内部封装的原始指针的比较运算符(即它们为get()返回的值调用相同的运算符)。它们都将nullptr作为参数进行了重载。因此,您可以检查是否存在有效的指针,甚至可以检查原始指针是否小于或大于nullptr。

    获取删除器

    get_deleter()返回指向删除器的指针(如果有的话),否则返回nullptr。 只要共享指针拥有该删除器,该指针就有效。 但是,要获取删除器,必须将其类型作为模板参数传递。 例如:

    auto del = [] (int* p) {
                   delete p;
               };
    std::shared_ptr<int> p(new int, del);
    decltype(del)* pd = std::get_deleter<decltype(del)>(p);

    注意,共享指针不提供release()操作来放弃所有权并将对象的控制权返回给调用者,原因是其他共享指针可能仍然拥有对象。

    别名构造函数

    带有另一个共享指针和另一个原始指针的构造函数是所谓的别名构造函数,它使你可以捕获一个对象拥有另一个对象的事实。 例如:

    struct X
    {
        int a;
    };
    shared_ptr<X> px(new X);
    shared_ptr<int> pi(px, &px->a);

    类型X的对象“拥有”其成员a,因此要创建指向a的共享指针,你需要通过使用别名构造函数将其附加到其引用计数上,以使周围的对象保持活动状态。 还存在其他更复杂的示例,例如引用容器元素或共享库符号。

    程序员必须确保两个对象的生存期匹配,否则可能会出现悬空指针或资源泄漏。 例如:

    shared_ptr<X> sp1(new X);
    shared_ptr<X> sp2(sp1, new X); //错误: 这个X的删除器永远不会被调用
    sp1.reset(); //删除第一个X,让sp1变空
    shared_ptr<X> sp3(sp1, new X); //use_count() == 0, 但get()!=nullptr

    make_shared()和allocate_shared()

    make_shared()和allocate_shared()优化了共享对象及其关联的控制块(例如,维护使用计数)的创建。

    shared_ptr<X>(new X(...))

    以上代码执行两次内存分配:一次内存分配给X,一次内存分配给控制块,例如由共享指针管理其使用计数。 

    make_shared<X>(...)

    上面创建共享指针的方法速度更快,仅执行一次内存,并且更安全,因为不会发生X内存分配成功但控制块内存分配失败的情况。 allocate_shared()允许传递你自己的分配器作为第一个参数。

    强制类型转换

    共享指针强制转换运算符允许将共享指针中包裹的指针强制转换为其他类型。 语义与相应的运算符相同,得到的是另一个不同类型的共享指针。 注意,不能使用普通的强制转换运算符,因为它会导致未定义的行为:

    shared_ptr<void> sp(new int); //智能指针内部保存void*指针
    ...
    shared_ptr<int>(static_cast<int*>(sp.get())) //错误:未定义的行为
    static_pointer_cast<int*>(sp) //正确

    线程安全接口

    通常,共享指针shared_ptr不是线程安全的。因此,为避免由于数据竞争而导致的未定义行为,当共享指针在多个线程中引用同一对象时,必须使用互斥或​​锁等技术。

    对应于普通指针的C风格原子接口,标准库提供了共享指针的重载版本。该接口允许多个线程同时操作共享指针。注意,这只意味着并发访问指针,而不是对它们所引用的值。

    例如:

    std::shared_ptr<X> global; //创建空的共享指针
    void foo()
    {
        std::shared_ptr<X> local{new X};
        ...
        std::atomic_store(&global, local);
    }

    下表列出了共享指针shared_ptr的高级原子操作。

    操作结果
    atomic_is_lock_free(&sp)如果sp的原子接口是无锁的,则返回true
    atomic_load(&sp)返回sp
    atomic_store(&sp,sp2)使用sp2对sp进行赋值
    atomic_exchange(&sp,sp2)交换sp与sp2的值

    使用错误

    尽管共享指针shared_ptr指针改善了程序安全性,但是由于通常会自动释放与对象关联的资源,所以当不再使用对象时可能会出现问题。 
    例如,必须确保只有一组共享指针拥有一个对象。 如下代码所示:

    int* p = new int;
    shared_ptr<int> sp1(p);
    shared_ptr<int> sp2(p); //错误:两个共享指针同时管理int内存

    以上代码的问题在于,当sp1和sp2失去对p的所有权时,它们都将释放关联的资源(调用delete),这将导致关联资源的释放会执行两次。 因此,在创建具有关联资源的对象时,应该始终直接初始化智能指针:

    shared_ptr<int> sp1(new int);
    shared_ptr<int> sp2(sp1);

    也可能间接发生此问题。 在《C++智能指针3——弱指针weak_ptr详解》的示例中,假设要为Person引入一个成员函数,该函数既创建了从孩子到父母的引用,又创建了从父母到孩子的应用:

    shared_ptr<Person> pMom(new Person(sName + "的母亲"));
    shared_ptr<Person> pDad(new Person(sName + "的父亲"));
    shared_ptr<Person> pKid(new Person(sName));
    pKid->setParentsAndTheirKids(pMom, pDad);

    下面代码是setParentsAndTheirKids()的简单实现:

    class Person {
        public:
            ...
            void setParentsAndTheirKids (shared_ptr<Person> pMother = nullptr,
                                         shared_ptr<Person> pFather = nullptr) {
                m_pMother = pMother;
                m_pFather = pFather;
                if (pMother != nullptr) {
                    pMonther->kids.push_back(shared_ptr<Person>(this)); //错误
                }
                if (pFather != nullptr) {
                    pFather->kids.push_back(shared_ptr<Person>(this)); //错误
                }
            }
            ...
    };
    

    以上代码的问题在于使用this创建共享指针。 这样做是因为要设置成员父亲和母亲的孩子。但要做到这一点,需要一个指向孩子的共享指针,而手边又没有这个指针。 然而,使用this创建新的共享指针并不能解决问题,因为这样打开了一个新的所有者组。

    解决此问题的一种方法是将共享指针作为第三个参数传递给孩子,不过C++标准库提供了另一个选项:类std::enable_shared_from_this<>。 可以使用类std::enable_shared_from_this<>派生自己的类,该类表示由共享指针管理的对象,并将类名作为模板参数传递。 这样可以使用派生成员函数shared_from_this()在此基础上创建正确的共享指针shared_ptr:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class Person : public enable_shared_from_this<Person> {
      public:
        string m_sName;
        shared_ptr<Person> m_pMother;
        shared_ptr<Person> m_pFather;
        vector<weak_ptr<Person>> m_oKids;  // weak pointer !!!
    
        Person (const string& sName)
         : m_sName(sName) {
        }
    
        void setParentsAndTheirKids (shared_ptr<Person> pMother = nullptr,
                                     shared_ptr<Person> pFather = nullptr) {
            m_pMother = pMother;
            m_pFather = pFather;
            if (pMother != nullptr) {
                pMother->m_oKids.push_back(shared_from_this());
            }
            if (pFather != nullptr) {
                pFather->m_oKids.push_back(shared_from_this());
            }
        }
    
        ~Person() {
          cout << "删除 " << m_sName << endl;
        }
    };
    
    shared_ptr<Person> initFamily (const string& sName)
    {
        shared_ptr<Person> pMom(new Person(sName + "的母亲"));
        shared_ptr<Person> pDad(new Person(sName + "的父亲"));
        shared_ptr<Person> pKid(new Person(sName));
        pKid->setParentsAndTheirKids(pMom, pDad);
        return pKid;
    }
    
    int main()
    {
        string sName = "张三";
        shared_ptr<Person> pPerson = initFamily(sName);
        cout << sName << "家已经存在" << endl;
        cout << "- " << sName << "被分享" << pPerson.use_count() << "次" << endl;
        cout << "- 张三母亲的地一个孩子的名字是:"
             << pPerson->m_pMother->m_oKids[0].lock()->m_sName << endl;
    
        sName = "李四";
        pPerson = initFamily(sName);
        cout << sName << "家已经存在" << endl;
    }

    注意,不能在构造函数内部调用shared_from_this(),如果这么做结果是运行时错误:

    class Person : public std::enable_shared_from_this<Person> {
        public:
            ...
            Person (const string& sName,
                    shared_ptr<Person> pMother = nullptr,
                    shared_ptr<Person> pFather = nullptr)
                : m_sName(sName), m_pMother(pMother), m_pFather(pFather) {
                if (pMother != nullptr) {
                    pMother->kids.push_back(shared_from_this()); //错误
                }
                if (pFather != nullptr) {
                    pFather->kids.push_back(shared_from_this()); //错误
                }
            }
            ...
    };

    上面代码的问题在于,在Person构造结束时,共享指针shared_ptr将自身存储在Person基类的私有成员中enable_shared_from_this <>。

    因此,在初始化共享指针对象的构造过程中,绝对没有办法创建共享指针的循环引用。 

    展开全文
  • 好长一段时间没明白共享指针的理解和使用,今天认认真真查了一些资料,搞懂了很多。在这里整理了一下两个链接的内容。 主要参考链接: https://blog.csdn.net/u011866460/article/details/42027457 ...

         好长一段时间没明白共享指针的理解和使用,今天认认真真查了一些资料,搞懂了很多。在这里整理了一下两个链接的内容。

    主要参考链接:

           https://blog.csdn.net/u011866460/article/details/42027457

          https://blog.csdn.net/shaosunrise/article/details/85228823

     

           共享指针 (shared_ptr) 是现在的 Boost 库中提供的,并且应该是将来 C++1x 的标准库中提供的一个模板类。在此之前,ISO/IEC 14882:2003 标准库 <memory> 中的“自动指针 (auto_ptr)”也有类似的功能。显然 shared_ptr 要比 auto_ptr 从功能上来说应该强大一些。这篇文章主要介绍 shared_ptr 的用法及注意事项。

    1. shared_ptr 的功能

            shared_ptr 主要的功能是,管理动态创建的对象的销毁。它的基本原理就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向某对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。

            与普通指针相比,共享指针对象重载了 * 、-> 和==运算符, 所以你可以像通常的指针一样使用它。没有重载+、-、++、--、[ ]等运算法。

    2. shared_ptr 的原理

          它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。
    每个 shared_ptr 对象在内部指向两个内存位置:
          1、指向对象的指针。
          2、用于控制引用计数数据的指针。(这里的引用计数为指向该内存块的共享指针个数
    共享所有权如何在参考计数的帮助下工作:
         1、当新的 shared_ptr 对象与原共享指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
         2、当任何 与之相关的shared_ptr 对象被析构时,例如局部函数调用结束,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除堆中对应内存。

    3. shared_ptr 所在库

    3.1. 对于 Visual C++ 2010

         目前,Visual C++ 2010 的 <memory> 库里,已经包含了 shared_ptr 模板类,也就是说,你可以直接这样写:

          #include <memory>

    3.2. 对于其它支持 ISO/IEC 14882:2003 标准的编译器

           而 GNU G++ 的标准库中还没有支持(毕竟是将来的标准),如果在 G++ 中想使用 shared_ptr, 还是得用到 Boost 库,就是说,在 G++ 里,你得这样写:

         #include <boost/shared_ptr.hpp>

    4. shared_ptr 对象的构造

           保险起见,你应该仅从以下几种途径构造一个共享指针(以下例子中若没特殊说明,T 就代表共享指针所指向的对象的类型):

    4.1. 使用空参数构造函数构造

          也就是说,你可以直接定义一个 shared_ptr 而不指定构造函数的内容:

           1

    std::shared_ptr<T> ptr;

          这样做的话,ptr 的意义就相当于一个 NULL 指针。当你试图在一个空指针上做类似于 *ptr 或者 ptr->xx 之类的东西的时候,应该会收到异常的。

    4.2. 直接从 new 操作符的返回值构造

          用代码来表示,就是可以这样使用:

          1

    std::shared_ptr<T> ptr(new T());

           上面这行代码在堆里创建了两块内存:1.存储T。2.用于引用计数的内存,管理附加此内存的shared_ptr对象的计数,最初计数将为1.

           因为带有参数的shared_ptr的复制构造函数(4.3)是explicit类型的,所以不能像这样 std::shared_ptr<T> ptr = new T();隐式调用它构造函数。

           但是链接里说构造新的shared_ptr对象的最佳方法是使用std::make_shared类模板。因为 1)它一次性为T对象和用于引用计数的数据都分配了内存,而new操作符只是为T分配了内存(没理解,求高人解答)。2)它可以避免一些由堆指针或者new分配指针导致的错误。

        1

          std::shared_ptr<T> p1=std::make_shared<T> ();

    4.3. 使用复制构造函数(或等号重载),从其它 shared_ptr 的对象构造

           一种显然的情况是这样的:

       1
       2

    std::shared_ptr<T> ptr1(new T()); // 本行与 3.1. 中的构造方法是一样的,引用计数为1
    std::shared_ptr<T> ptr2(ptr1);    // 这就是使用复制构造函数的方法,会让引用计数加 1

           还有,shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用复制构造函数。

           过程如下:作函数实参时,将指针执行复制构造函数传入函数体内,因此该内存块的引用计数+1;当作为函数返回值时,复制构造函数将内存地址传递给新指针,引用计数+1,然后,局部指针执行析构,引用计数-1。

    4.4. 从 shared_ptr 提供的类型转换 (cast) 函数的返回值构造

            shared_ptr 也可以类型转换,有关类型转换的详情参见下面的 6. 此处假设 B 是 A 的子类,那么,根据C++继承与派生中的知识,B 的指针当然是可以转换成 A 的指针的。在共享指针里,应该这样做:

      1
      2

    std::shared_ptr<B> ptrb(new B());
    std::shared_ptr<A> ptra( dynamic_pointer_cast<A>(ptrb) );  //本质还是复制构造

     

    5. shared_ptr 的“赋值”

           shared_ptr 也可以直接赋值,但是必须是赋给相同类型的 shared_ptr 对象,而不能是普通的 C 指针或 new 运算符的返回值。当共享指针 a 被赋值成 b 的时候,如果 a 原来是 NULL, 那么直接让 a 等于 b 并且让它们指向的东西的引用计数加 1; 如果 a 原来也指向某些东西的时候,如果 a 被赋值成 b, 那么原来 a 指向的东西的引用计数被减 1, 而新指向的对象的引用计数加 1. 就是说以下代码是允许的:

       1
       2
       3

    std::shared_ptr<T> a(new T());
    std::shared_ptr<T> b(new T());
    a = b; // 此后 a 原先所指的对象会被销毁,b 所指的对象引用计数加 1

    shared_ptr 的对象在构造之后,可以被赋予空值,此时使用的应该是 reset() 函数或者nullptr,如:

      1
      2

      3

    std::shared_ptr<T> a(new T());
    a.reset();         // 此后 a 原先所指的对象的引用计数-1,并且 a 会变成 NULL。这里内存会被销毁

    a=nullptr;        //同上。推荐多用

    当然理论上也可以这样写:

      1
      2

    std::shared_ptr<T> a(new T());
    a = std::shared_ptr<T>(); // ,相当于给 a 赋值一个新构造的 无名shared_ptr对象, 也就是 NULL

    6. shared_ptr 的类型转换

           shared_ptr 有两种类型转换的函数,一个是 static_pointer_cast, 一个是 dynamic_pointer_cast. 其实用法真的和 C++ 提供的 static_cast 和 dynamic_cast 很像,再结合 4.4. 的代码和以下类似的代码,几乎没什么好讲的:

      1
      2
      3

    std::shared_ptr<A> ptra;
    std::shared_ptr<B> ptrb(new B());
    ptra = dynamic_pointer_cast<A>(ptrb);

    7. 从 shared_ptr 的对象获得传统 C 指针

        很简单,可以这样用:

        1
        2

    std::shared_ptr<T> ptr(new T());
    T *p = ptr.get(); // 获得传统 C 指针

         建议少用get()函数,因为如果在shared_ptr析构之前手动调用了delete函数,同样会导致类似的错误。

    8. shared_ptr 的常见的其它用法(重要)

    8.1 共享指针的重置

    比如,“我想让一个已经构造好的共享指针,丢弃掉原来所指的对象(或者让其引用计数减 1),然后指向一个新的 new 出来的对象,该怎么办?”参考如下代码:

         1
          2

    std::shared_ptr<T> ptr(new T());
    ptr.reset(new T()); // 原来所指的对象会被销毁

    8.2 NULL检测
            当我们创建 shared_ptr 对象而不分配任何值时,它就是空的,即地址为000000000;普通指针不分配空间的时候相当于一个野指针,指向垃圾空间,且无法判断指向的是否是有用数据。
           shared_ptr 检测空值方法

        1
        2

        3 

        4  

        5 

        6 

        7   

        8 

        9

    std::shared_ptr<T> ptr3;
    if(!ptr3)
        std::cout<<"Yes, ptr3 is empty" << std::endl;
    if(ptr3 == NULL)
        std::cout<<"ptr3 is empty" << std::endl;
    if(ptr3 == nullptr)
        std::cout<<"ptr3 is empty" << std::endl;

    if(ptr3==0)

        std::cout<<"ptr3 is empty" << std::endl;

     

    9. shared_ptr 的错误用法

          一定要注意,本节所述所有方法,都是错误的!

    9.1. 在“中途”使用传统 C 指针构造共享指针

          所谓在中途,指的就是不从 new 的返回值直接构造共享指针,比如从 this 指针构造自己的共享指针等。

    9.2. 从一个对象的传统 C 指针,构造出两个或以上的共享指针

         其实这种和 9.1. 也是类似的,或者说,这种情况是 9.1. 的一种具体情况,比如,下面的代码是错误的:

       1
       2
       3

    T *a = new T();
    shared_ptr<T> ptr1(a);
    shared_ptr<T> ptr2(a);

        这样的话,ptr1 和 ptr2 的引用计数是单独算的,它们任意一个对象在析构的时候,都会销毁 a 所指的对象,a就为悬空指针,所以,这个对象会被“销毁两次”。因此报错。(make_shared类模板可以避免)

    9.3 不要用栈中的指针构造shared_ptr对象

         shared_ptr默认的构造函数中使用的是delete来删除关联的内存,所以构造的时候也必须使用new出来的堆空间的指针。如果是栈中的指针,当shared_ptr对象超出作用域调用析构函数delete内存时,会报错。(make_shared类模板可以避免)

    10. shared_ptr 的局限

         有关运行效率的问题在这里就不讨论了。其它方面,shared_ptr 的构造要求比较高,如果对象在创建的时候没有使用共享指针存储的话,之后也不能用共享指针管理这个对象了。如果有引用循环 (reference cycle), 也就是对象 a 有指向对象 b 的共享指针,对象 b 也有指向对象 a 的共享指针,那么它们都不会被析构。当然的,shared_ptr 也没有办法和 Garbage Collecting 比较,毕竟如果运行库能够干预,还是有算法可以检查到引用循环的。(例如求强连通分量的算法。)

         尤其,在类的成员函数的编写的时候,有时我们经常希望得到“自己”的共享指针,但是这往往是无法得到的。此时也不能够从 this 指针构造自己的共享指针(参见 9.1.),所以有时很憋闷。

    11. 总结

         实际上上面这么多注意事项,中心思想就是一个:让 shared_ptr 正确地记录对象被引用次数。如果能悟出一点 shared_ptr 的工作原理,基本上不会弄出太危险的事情来。

    展开全文
  • C++多线程与共享指针

    千次阅读 2020-06-21 21:56:58
    文章目录C++多线程与共享指针一、C++多线程使用2.1 thread2.2 pthread C++多线程与共享指针 一、C++多线程使用 2.1 thread <thread> 该头文件包含有std::thread类与std::this_thread类。以及管理线程的函数。...

    C++多线程与共享指针

    一、C++多线程使用

    • 多线程应注意线程之间的数据传递,以及数据的共享问题,对共享数据操作的时候应使用锁来解决。
    • 经验:采用初始化时创建线程,然后使用队列网线程中传数据,效率会比需要使用线程时,创建,用完释放的效率低。

    2.1 thread

    • <thread> 该头文件包含有std::thread类与std::this_thread类。以及管理线程的函数。是实现线程的主要文件。
    • <atomic> 该头文件包含有std::atomic和std::atomic_flag类,是实现原子操作的的主要文件。
    • <mutex> 包含互斥相关的类与函数。
    • <future> 包含有future类及相关的函数。
    • <condition_variable> 包含有条件变量的类。
    #include<thread>
    #include<mutex>
    
    std::thread t(func);          //无参数函数
    std::thread t(func, parameter)     //带参数函数
    std::thread t(&class::func, this, parameter)     //类内函数新起线程   第一个参数类函数指针,第二个参数对象指针
    
    //如果要参数要被改变。采用std::ref
    t.joinable();   
    t.join();
    t.detach();
    //创建线程后,可选用join和detach
    //joinable:表示程序是否可执行,如果线程已经执行完,则不可join
    //detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。detach将线程与主线程剥离,不再受主线程管理。
    //注意:在释放各个对象时,请确保detach的线程已完成
    //join方式,等待启动的线程完成,才会继续往下执行。join的意思为进入到线程里
    
    //注意:线程的启动与否与join和detach无关,创建线程后,线程已经启动开始运行
    
    std::thread t3(move(t1));       //将线程从t1转移给t3
    t1.swap(t2);     //交换线程
    
    std::this_thread::sleep_for(chrono::milliseconds(10));     //线程等待
    
    t.get_id()//获取线程id
    auto mainThreadId = std::this_thread::get_id();      //在当前线程获取id
    
    //std::mutext:   独占的互斥量,不能递归使用
    
    //std::timed_mutex:   带超时的互斥量,不能递归使用。
    
    //std::recursive_mutex:  递归互斥量,不带超时功能。
    
    //std::recursive_timed_mutex:  带超时的递归互斥量
    
    std::mutex g_lock; 
    g_lock.lock();           //锁
    g_lock.unlock();        //去锁
    //互斥锁:
    //lock() :第一个线程进去后,后面的线程会进入阻塞休眠状态,被放入到阻塞队列中。
    //unlock():加锁的线程执行完后,会解锁,然后去阻塞队列中唤醒阻塞线程
    //适用于锁的粒度大的场景
    //自旋锁:
    //lock():第一个线程进去,后面的线程在循环空转检查。
    //unlock():第一个加锁的线程解锁,后面的线程检测到就可以被cpu调度。
    
    std::lock_guard<std::mutex> lock(mutex);       //获取互斥量
    
    std::recursive_mutex mutex;
    std::lock_guard<std::recursive_mutex> lock(mutex);   // 获取互斥量
    
    t1.native_handle();      //返回 native handle(由于 std::thread 的实现和操作系统相关,因此该函数返回与 std::thread 具体实现相关的线程句柄,
    
    t1.hardware_concurrency(); // 检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint)
    
    std::this_thread::yield();  //当前线程放弃执行,操作系统调度另一线程继续执行。
    
    sleep_until();// 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。
    
    

    2.2 pthread

    #include <pthread.h>
    
    // 定义线程的 id 变量,多个变量使用数组
    std::pthread_t tids[NUM_THREADS];
    
    
    std::pthread_create(thread, attr, start_routine, arg);
    int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
    //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
    //thread	指向线程标识符指针。
    //attr	一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
    //start_routine	线程运行函数起始地址,一旦线程被创建就会执行。
    //arg	运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。
    
    std::pthread_exit(NULL);    //终止线程
    
    std::pthread_join(threadid, status)     //连接线程,子程序阻碍调用程序,直到指定的 threadid 线程终止为止。
    
    std::pthread_detach(threadid);      //分离线程
    
    std::pthread_attr_t attr;    //定义线程属性
    // 初始化并设置线程为可连接的(joinable)
    std::pthread_attr_init(&attr);    
    std::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    
    // 删除属性
    std::pthread_attr_destroy(&attr);
    

    二、共享指针shared_ptr

    • 智能指针是存储指向动态分配对象指针的类。三种智能指针:
      std::shared_ptr/std::unique_ptr/std::weak_ptr.
    • 使用时需#include<memory>
    • 不用自己手动释放空间,其内部有一个计数器。

    2.1 初始化

    std::shared_ptr<int> p(new int(1));
    std::shared_ptr<int> p2 = p;    //p和p2共享同一段内存, 如果p2申请了空间,也会释放p2,p的次数加1
    
    std::shared_ptr<int> ptr;
    ptr.reset(new int(1));
    
    int *p1 = new int[2];
    std::shared_ptr<int> p3(p1); //p1和p3共享同一段内存
    
    //使用make_shared.  更高效
    auto p1 = std::make_shared<int>(10);
    auto p2 = std::make_shared<string>(10,"s");
    auto p3 = std::make_shared<Struct>();
    

    2.2 make_shared和new+shared_ptr的区别

    std::shared_ptr<int> ptr(new int(1));  //方式1
    
    std::shared_ptr<int> ptr = std::make_shared<int>()  //方式2
    
    //方式1:std::shared_ptr构造函数会执行两次内存申请,两次空间申请均在堆上,且不连续。
    //而方式2:std::make_shared则执行一次,申请一次空间。只申请一次,堆空间连续。
    
    

    2.3 获取原始指针

    std::shared_ptr<int> ptr(new int(1));
    int *p = ptr.get();
    

    2.4指定删除器

    void deleteIntPtr(int* p)
        {
            delete p;
        }
    std::shared_ptr<int> p5(new int,deleteIntPtr);
    
    //使用lambda表达式
    std::shared_ptr<int> p6(new int,[](int* p){delete p;});
    std::shared_ptr<int> p6(new int[10],[](int* p){delete[] p;});    //数组
    std::shared_ptr<int> p7(new int[10],std::default_delete<int[]>);   //使用default_delete作为删除器
    
    //封装,使得共享指针支持共享数组
    template<typename T>
    std::shared_ptr<T> make_shared_array(size_t size)
    {
        return std::shared_ptr<T>(new T[size],std::default_delete<T[]>());
    }
    std::shared_ptr<int> p8 = make_shared_array<int>(10);
    std::shared_ptr<char> p9 = make_shared_array<char>(10);
    

    2.5 注意事项

    • 不能使用原始指针初始化多个shared_ptr
    int* p11 = new int;
    std::shared_ptr<int> p12(p11);
    std::shared_ptr<int> p13(p11);
    
    • 不要在实参中创建shared_ptr,应该先创建一个智能指针,再使用
    deleteIntPtr(std::shared_ptr<int>(new int));//错误的
    std::shared_ptr<int> p14(new int());
    deleteIntPtr(p14);//OK
    
    • 要通过shared_from_this()返回this指针
    struct A
    {
        std::shared_ptr<A> getSelf()
        {
            return std::shared_ptr<A>(this);    //错误,
        }
    };
    int main()
    {
        std::shared_ptr<A> sp1(new A);
        std::shared_ptr<A> sp2 = sp1->getSelf();//会导致重复析构
       return 0;
    }
    
    
    // 正确使用方式
    class A:public std::enable_shared_from_this
    {
    public:
        std::shared_ptr<A> getSelf()
        {
            return shared_from_this();
        }
    };
    
    • 免循环使用,如下A/B两个指针都不会被删除会有内存泄漏(可用weak_ptr解决)
    struct A;
    struct B;
    struct A
    {
        std::shared_ptr<B> bptr;
        ~A(){cout << "A is deleted!"<<endl;}
    };
    struct B
    {
        std::shared_ptr<A> aptr;
        ~B() {cout << "B is deleted!"<<endl;}
    };
    int main()
    {
        {
            std::shared_ptr<A> ap(new A);
            std::shared_ptr<B> bp(new B);
            ap->bptr = bp;
            bp->aptr = ap;
         
         }
    }
    
    • 避免使用栈上的指针(局部变量),使用new申请的堆上的指针。

    2.6 共享指针的计数规则

    • 每个共享指针都有一个引用计数,记录着有多少指针指向自己。
    • shared_ptr的析构函数:递减它所指向的对象的引用计数,如果引用计数变为0,就会销毁对象并释放相应的内存。
    • 引用计数的变化:决定权在shared_ptr,而与对象本身无关。

    2.6.1计数增加

    sp.use_count()返回对象sp的引用计数。

    shared_ptr<int> sp;                       //空智能指针
    shared_ptr<int> sp2 = make_shared<int>(3);
    shared_ptr<int> sp3(sp2);
    cout << sp.use_count() << endl;         //输出0
    cout << sp2.use_count() << endl;        //输出2
    

    拷贝一个shared_ptr,其所指对象的引用计数会递增加

    • 用一个shared_ptr初始化另一个shared_ptr
    • 用一个shared_ptr给另一个shared_ptr赋值
    • 将shared_ptr作为参数传递给一个函数
    • shared_ptr作为函数的返回值

    2.6.2计数减少

    • 给shared_ptr赋予一个新值
    • shared_ptr被销毁(如离开作用域)
    • sp.reset() 将计数减1。
    • p.reset(new int(1))
      指针与指向原来对象对应的引用计数减1,若引用计数减为0则释放该对象内存;
      指针与指向新对象对应的引用计数加1;

    2.6.3 易错点

    多个局部shared_ptr指向该对象,那么函数结束时对象的引用计数就不应该只减1。例如:

    shared_ptr<int> init()
    {
        shared_ptr<int> sp2 = make_shared<int>(3);
        shared_ptr<int> sp3(sp2);
        cout << sp2.use_count() << endl;        //输出2
        return sp2;                             //返回sp2,故引用计数递增,变为3(返回的copy的一个shared_ptr指针,sp2和sp3都会被销毁)
    }                                           //sp2和sp3离开作用域,引用计数减2,变为1
     
    int main()
    {
        auto p = init();                        //此处赋值的拷贝与return处的拷贝是一致的
        cout << p.use_count() << endl;          //输出1
        return 0;
    }
    

    2.7 shared_ptr线程安全问题

    • 同一个shared_ptr被多个线程读,是线程安全的;
    • 同一个shared_ptr被多个线程写,不是线程安全的;
    • 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。
      对于线程中传入的外部shared_ptr对象,在线程内部进行一次新的构造,例如: sharedptr AObjTmp = outerSharedptrObj;
      可参考
      线程安全

    三、智能指针weak_ptr

    • weak_ptr用于解决“引用计数”模型循环依赖问题
    • weak_ptr的使用不会引起计数的增加。
    • weak_ptr不影响对象的生命周期。

    不影响计数与生命周期

    //default consstructor
    weak_ptr<string> wp;
    {
        shared_ptr<string> p = make_shared<string>("hello world!\n");
        //weak_ptr对象也绑定到shared_ptr所指向的对象。
        wp = p;   
        cout << "use_count: " <<wp.use_count() << endl;
    }
    //wp是弱类型的智能指针,不影响所指向对象的生命周期,
    //这里p已经析构,其所指的对象也析构了,因此输出是0
    cout << "use_count: " << wp.use_count() << endl;
    
    use_count: 1
    use_count: 0
    

    3.1 weak_ptr的基本使用

    • weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象.
    • expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
    • lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
    • use_count 返回与 shared_ptr 共享的对象的引用计数.
    • reset 将 weak_ptr 置空.
    • weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

    例子:

    void test_valid(weak_ptr<string> &wp)
    {
        if(shared_ptr<string>  smptr2 = wp.lock())
        {
            cout << "the shared_ptr is valid\n";
        }
        else
        {
            cout << "the shared_ptr is not valid\n";
        }
        //检查被引用的对象是否已删除 false 仍存在  true 释放
        if(!wp.expired())
        {
            //it is getting valid shared_ptr obj now;
            shared_ptr<string>  smptr1 = wp.lock();
            cout << "   get obj value: " << *smptr1;
        }
    }
    
    int main()
    {    
        shared_ptr<string> p = make_shared<string>("hello world!\n");
    
        //default consstructor
        weak_ptr<string> wp1;
        //copy constructor
        weak_ptr<string> wp2(p);
        //assign constructor
        weak_ptr<string> wp3 = wp2;
        
        test_valid(wp2);
        //释放被管理对象的所有权, 调用后 *this 不管理对象
        wp2.reset();
        test_valid(wp2);
        return 0;
    }
    

    3.2 weak_ptr解决循环引用的问题

    • 循环引用
    class Parent
    {
    public:
        shared_ptr<Child> child;
    };
    class Child
    {
    public:
        shared_ptr<Parent> parent;
    };
    shared_ptr<Parent> pA(new Parent);
    shared_ptr<Child> pB(new Child);
    pA->child = pB;
    pB->parent = pA;
    
    • weak_ptr指向shared_ptr指针指向的对象的内存,却并不拥有该内存。
    • 但是,使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值(nullptr)。由于weak_ptr是指向shared_ptr所指向的内存的,所以,weak_ptr并不能独立存在。
    • 使用weak_ptr查看共享指针是否失效,来判断是否有内存泄漏
    include <iostream>
    #include <memory>
    using namespace std;
    
    void Check(weak_ptr<int> &wp)
    {
        shared_ptr<int> sp = wp.lock(); // 重新获得shared_ptr对象
        if (sp != nullptr)
        {
            cout << "The value is " << *sp << endl;
        }
        else
        {
            cout << "Pointer is invalid." << endl;
        }
    }
    
    int main()
    {
        shared_ptr<int> sp1(new int(10));
        shared_ptr<int> sp2 = sp1;
        weak_ptr<int> wp = sp1; // 指向sp1所指向的内存
    
        cout << *sp1 << endl;
        cout << *sp2 << endl;
        Check(wp);
    
        sp1.reset();
        cout << *sp2 << endl;
        Check(wp);
    
        sp2.reset();
        Check(wp);
    
        system("pause");
        return 0;
    }
    

    四、智能指针unique_ptr

    • 用于防止内存泄漏的智能指针
    • 独享被管理对象指针所有权的智能指针,关联原始指针的唯一所有者,因此不能复制unique_ptr指针。因此也没有引用计数。
    • unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
    • unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
    //创建一个空的unique_ptr的对象
    std::unique_ptr<int> ptr1;
    
    //检查unique_ptr是否为空
    // 方法1
    if(!ptr1)
    	std::cout<<"ptr1 is empty"<<std::endl;
    // 方法2
    if(ptr1 == nullptr)
    	std::cout<<"ptr1 is empty"<<std::endl;
    
    //使用原始指针创建unique_ptr
    std::unique_ptr<Task> taskPtr(new Task(22));
    
    //使用make_unique创建
    std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
    
    //获取被管理对象的指针
    Task *p1 = taskPtr.get();
    
    //重置,它将释放delete关联的原始指针并使unique_ptr 对象为空。
    taskPtr.reset();
    
    //unique_ptr不能复制
    
    // 编译错误 : unique_ptr 不能复制
    std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
    // 编译错误 : unique_ptr 不能复制
    taskPtr = taskPtr2; //compile error
    
    //转移 unique_ptr 对象的所有权,转移后,taskPtr2为空
    std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
    
    //释放原始指针所有权,返回原始指针。释放所有权,并没有delete原始指针,reset()会delete原始指针。
    Task * ptr = taskPtr5.release();
    
    展开全文
  • 锈共享ptr Rust的共享指针实现。 大多数源代码是从Rust Rc指针复制而来的,但要复制get_mut函数。 该项目仅用于学习Rust的培训。
  • 模板共享指针(shared_ptr)原理实现

    千次阅读 2017-05-04 23:35:03
    最近在书中看到关于智能指针的描述,由于之前没有使用过智能...共享指针在拷贝函数中拷贝已有的指针对象参数地址达到共享数据(简单的说就是一块类对象地址由多个指针同时指向并且使用); 2.共享指针内部通过计数形
  • C++: 共享指针shared_ptr使用

    千次阅读 2018-09-16 08:06:31
    共享指针 (shared_ptr) 是现在的 Boost 库中提供的,并且应该是将来 C++1x 的标准库中提供的一个模板类。在此之前,ISO/IEC 14882:2003 标准库 &lt;memory&gt; 中的“自动指针 (auto_ptr)”也有类似的...
  • 记得刚开始使用共享指针的时候,不理解共享指针的reset(), std::make_shared<T>(args....)和构造函数: 实际上本质上是一样的,都是将一个指针交给共享指针对象管理,只是std::make_shared<T>(args...)...
  • [UE4]共享指针

    千次阅读 2019-03-01 19:04:26
    共享指针 本页面的内容: 声明和初始化 解引用和访问 比较 转换 共享指针 是一种非侵入式的、引用计数的特殊类型智能指针,它既支持强引用也支持弱引用。共享指针本身包含了基本智能指针所有的优点,它们可以...
  • 1.不使用共享指针的情况 #include <iostream> #include <memory> #include <string.h> class A{ public: A() { std::cout << "create A." << std::endl; } ...
  • 先记录下普通指针、智能指针、共享指针,免得忘记,有空来系统学习。 在一个论坛的问答中,别人的回答问题: 普通指针就是这样 - 它们指向某个地方的内存中的某些东西。谁拥有它?只有评论会让你知道。谁释放它...
  • C++共享指针的父类和子类转化

    千次阅读 2019-06-18 14:38:57
    1.能用共享指针就用共享指针,不用普通指针; 2.在工程一开始就用共享指针,不要定义一个普通指针后再转化; 以下代码转自OSChina,感谢原创者@宁宁爸,记在这里以便查阅。 #include <boost/shared_ptr.hpp&...
  • C++ std::shared_ptr共享指针示例

    千次阅读 2019-03-04 15:54:57
    std::shared_ptr 顾名思义,允许有多个owner对同一指针享有所有权,可通过reset或赋值操作变更所有权。 Objects of shared_ptr types have the ability of taking ownership of a pointer and share that ownership:...
  • Boost 共享指针 共享数组

    千次阅读 2016-06-21 14:10:56
    共享指针 shared_ptr已经作为技术报告1(TR1)的一部分被添加到标准。如果开发环境允许,可以使用memory中定义的std::shared_ptr。 在Boost C++库里,类名为boost::shared_ptr,定义在 boost::shared_ptr...
  • 共享指针,父类和子类之间互转 参考 例子 //foo(shared_ptr<Base>(bar)); //foo(static_pointer_cast<Base>(bar)); 子类转父类 1 -父类 IXXXEvent_t -子类 std::shared_ptr ptr = std::make_...
  • //前48位存共享指针的指针,16位存计数 } 获取指针函数是load SharedPtr load(std::memory_order order = std::memory_order_seq_cst) const noexcept { auto local = takeOwnedBase(order); return get_...
  • C++ 共享指针 shared_ptr

    千次阅读 2016-01-11 10:58:13
    shared_ptr 由于C++不像java一样有自动回收内存机制,new对象后,都要手动的delete掉,当程序员忘记delete的时候,可能会发生访问内存异常错误。shared_ptr是为了解决这类问题而提出的,它是一种共享指针
  • shared_ptr(共享指针)使用总结

    万次阅读 2014-04-15 12:33:31
    背景介绍 最近在coding的时候遇到这样的问题,一个两层的map,hashmap>,重新加载时需要将这个map释放,但是这个map使用时是...重新加载数据并且释放掉现在的hashmap,这个场景引起了我对shared_ptr共享指针的关
  • boost 共享指针

    千次阅读 2013-10-14 10:19:45
    boost 共享指针用于类成员 声明: shared_ptr _p; 构造时比较麻烦,你可以在构造函数中写: _p = shared_ptr ( new XXX() );
  • 1、pcl::PointIndices--&gt;pcl::PointIndices::Ptr的转化 pcl::PointIndices inliers; pcl::PointIndices::Ptr inliers_ptr(new pcl::PointIndices(inliers)); 2、pcl::PointIndices::Ptr--&...
  • C++ 智能指针(共享指针)实现 主要实现思想是引用计数法,在SharePtr类拷贝(Copy)和赋值(=)的时候引用计数加1,而在SharePtr类变量析构的时候引用计数减少1 实现细节 (1)SharePtr包裹类变量指针和引用计数指针int*...
  • 【UE4】共享(智能)指针用法

    千次阅读 2020-07-31 21:33:52
    文章目录一、基本概念二、UE4智能指针1. TSharedPtr2. TSharedRef3. TWeakPtr4. TUniquePtr5....一、基本概念 在UE4中,继承UObject的类...对此,我们可以使用UE4提供的共享指针来创建和管理这些类对象。C++的stl库中也提

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 309,988
精华内容 123,995
关键字:

共享指针