zoukankan      html  css  js  c++  java
  • 智能指针 01

    (读书笔记:全部摘抄自cPP标注库)

    C++ 11 中,标准库提供了两大类smart pointer:

    1. Class shared_ptr 实现共享式拥有(shared ownership)概念。多个 只能指针 可以指向相同的对象,改对象和其相关资源会在 “最后一个reference被销毁” 时释放。为了在结构较为复杂的情境中执行上述工作,标准库提供了weak_ptr ,bad_weak_ptr  和 enable_shared_from_this等辅助类。

    2,Class_unique_ptr 实现独占式拥有(exclusive ownership) 或 严格拥有(strict ownership)概念。保证同一时间内只有一个smart pointer可以指向该对象。你可以移交拥有权。他对于 避免资源泄露特别有用。

    c++98只让c++标准库提供一个smart pointer class : auto_ptr<>,其设计是为了执行现今的unique_ptr所提供的服务。然而由于当时缺乏语言特性如“针对构造和赋值”的move语义,以及其他瑕疵,这个class不易被理解且容易出错。因此在TR1引入clss shared_ptr,c++11引入class unique_ptr之后,auto_ptr成为c++11中被证实反对的成分,除非老旧代码需要编译,否则你不应使用它。

    所有smart pointer class 都被定义于头文件<memory>内。

    shard_ptr 的目标是没有蛀牙(在其所指向的对象不再被需要之后,自动释放与对象相关的资源)。

    使用shard_ptr 

    #include<iostream>
    #include<string>
    #include<vector>
    #include<memory>
    
    using namespace std;
    
    int main(){
        shared_ptr<string> pNico(new string("nico"));
        shared_ptr<string> pJutta(new string("jutta"));
    
        (*pNico)[0] = 'N';
        pJutta->replace(0,1,"J");
    
        vector<shared_ptr<string>> whoMadeCoffee;
        whoMadeCoffee.push_back(pJutta);
        whoMadeCoffee.push_back(pJutta);
        whoMadeCoffee.push_back(pNico);
        whoMadeCoffee.push_back(pNico);
        for(auto ptr : whoMadeCoffee) {
            cout << *ptr << "   ";
        }
        cout << endl;
    
        *pNico = " Nicolai";
    
        for(auto ptr : whoMadeCoffee) {
            cout << *ptr << "   ";
        }
        cout << endl;
    
        cout << "use_cout " << whoMadeCoffee[0].use_count() << endl;
    }

    需要注意的是,由于“接受单一pointer作为唯一实参" 的构造函数是explicit,所以这里不能使用赋值符,因为那样的话会被视为需要一个隐式转换,然而新式的语法是被接受的:

    shared_ptr<string> pNico = new string("nico");  //ERROR
    shared_ptr<string> pNico{new string("nico")};   //OK

    也可以使用便捷的 make_shared() :

    shared_ptr<string> pNico = make_shared<string>("nico");

    这种方式比较快,也比较安全,因为它使用一次而非二次分配:一次针对对象,另一次针对"shared pointer 用以控制对象" 的shared data。

    另一种写法是,先声明shard pointer ,然后对它赋值一个new pointer,然后不可以使用assignment操作符,必须改用reset():

    pNico3 = new string("nico");    //ERROR :no assignment for ordinary pointers
    pNico3.reset(new string("nico"));   //Ok

    定义一个Deleter

    定义一个deleter,例如让它在“删除被指向对象”之前先打印一条信息:

    shared_ptr<string> pNico(new string("nico"),
                             [](string * p){
                                 cout << "delete " << *p << endl;
                                 delete p;
                             });
    pNico = nullptr;  //pNico does not refer to string any longer
    whoMadeCoffee.resize(2);   // all copies of string in pNico are destoryed                   

    其中函数对象 D del参数是一个lambda表达式。

    关于shared_ptr 的构造函数,参考链接

    对付Array

    shared_ptr提供的default deleter 调用的是delete,不是delete[] ,这意味着局限性,只有当shared_ptr拥有“由new建立起来的单一对象”,default deleter才试用。然而很不幸,为array建立一个shared_ptr是可能的,却是错误的:

    std::shared_ptr<int> p(new int[10]); //ERROR,but compiles

    故,如果你试用new[]建立一个array of object ,必须自己定义自己deleter。你可以传递一个函数,或者函数对象,或者lambda,让它们针对传入的寻常指针调用delete[]。例如:

    shared_ptr<int> p(new int[10],
                            [](int *p) {
                                delete[] p;
                            });

    也可以试用为unique_ptr而提供的辅助函数作为deleter,其内调用deleter[]:

    std::shared_ptr<int> p(new int[10], std::default_delete<int[]> ());

    需要注意的是:shared_ptr 和unique_ptr 以稍稍不同的方式处理deleter。例如unique_ptr允许只传递对应的元素类型作为template实参,但这对shared_ptr就不行:

    std::unique_ptr<int[]> p(new int[10]);  //OK
    std::shared_ptr<int[]> p(new int[10]);  //ERROR:does not compile

    此外,对于 unique_ptr,你必须明确给予第二个template实参,指出自己的deleter:

    std::unique_ptr<int,void(*)(int *)> p(new int[10], [](int *p) {
                                delete[] p;
    });                       

    还需注意,shared_ptr不提供operator[]。至于unique_ptr,它有一个针对array的偏特化版本,该版本提供operator[] 取代 operator* 和 operator-> 。之所以有此差异是因为,unique_ptr在效能和弹性上进行了优化。

     其他析构策略

    eg1:假设我想确保“指向某个临时文件”之最末一个reference被销毁时,该文件即被移除:

    class FileDeleter{
    private:
        string filename;
    public:
        FileDeleter (const string& fn) : filename(fn){
        }
        void operator () (std::ofstream* fp){
            fp->close();
            std::remove(filename.c_str());
        }
    };
    
    
    int main(){
        //create and open temporary file:
        std::shared_ptr<std::ofstream> fp(new std::ofstream("tmpfile.txt"),FileDeleter("tmpfile.txt"));
        ...
    }

    eg2: 使用shared_ptr处理共享内存:

    #include<memory>
    #include<sys/mman.h>
    #include<fcntl.h>
    #include<unistd.h>
    #include<cstring>
    #include<cerrno>
    #include<string>
    #include<iostream>
    
    using namespace std;
    
    class SharedMemDetacher{
    public:
        void operator()(int* p){
            std::cout << "unlink /tmp1234" << std::endl;
            if(shm_unlink("/tmp1234") != 0)
            {
                std::cerr << "OOPS :shm_unlink() failed " << std::endl;
            }
        }
    };
    
    std::shared_ptr<int> getSharedMem(int num){
        void* mem;
        int shmfd = shm_open("/tmp1234",O_CREAT|O_RDWR,0777);
        if(shmfd < 0){
                    cout << "shmfd open fialed";
            throw std::string(strerror(errno));
    
        }
        if(ftruncate(shmfd,num*sizeof(int)) == -1) {
            throw std::string(strerror(errno));
            cout << "ftruncate failed 
    ";
        }
    
        mem = mmap(nullptr,num*sizeof(int),PROT_READ | PROT_WRITE,MAP_SHARED,shmfd,0);
        if(mem == MAP_FAILED){
            throw std::string(strerror(errno));
            cout << "map 失败
    ";
        }
        return std::shared_ptr<int>(static_cast<int*>(mem),SharedMemDetacher());
    }
    
    int main()
    {
        std::shared_ptr<int> smp(getSharedMem(100));
        for(int i = 0;i<100;++i){
            smp.get()[i] = i*42;
        }
        std::cout << "<return>" << std::endl;
        std::cin.get();
    
        //release memory here:
        smp.reset();
        //....
    }

    weak_ptr

    在一些情况下,shared_ptr并不是总是按照期望运行的。比如

    • 环式指向。比如两个shared_ptr互相指向对方,而一旦不存在其他reference指向它们,你想释放它们和其相应的资源。因为每个对象的use_count() 仍是1,所以shared_ptr不会释放数据。
    • 在一些情况下,你想“共享某个对象,而不拥有”。你要的语义是:reference的寿命比其所指向的对象寿命更长。因此shared_ptr绝不释放对象,而寻常pointer可能不会注意到他们所指向的对象已经不再有效。导致了“访问已被释放的数据”的风险。

    于是,标准库提供了class weak_ptr,允许“共享但不拥有”对象。这个class会建立一个shared_ptr。一旦最后一个拥有该对象的shread pointer失去了拥有权,任何weak pointer 都会自动成空。因此,在default 和copy构造函数之外,class weak_ptr只提供“接受一个shared_ptr”的构造函数。

    你不能使用操作符 * 和 -> 访问weak_ptr对象,而是必须另外建立一个shared pointer。

    class Person{
    public:
        string name;
        shared_ptr<Person> monther;
        shared_ptr<Person> father;
        vector<shared_ptr<Person>> kids;
    
        Person(const string& n, shared_ptr<Person> m = nullptr,
                shared_ptr<Person> f = nullptr) : name(n),monther(m),father(f){        
        }
    
        ~Person(){
            cout << "delete " << name << endl;
        }
    };
    
    shared_ptr<Person> initFamily(const string& name){
        shared_ptr<Person> mom(new Person(name + "'s mom"));
        shared_ptr<Person> dad(new Person(name + "'s dad"));
        shared_ptr<Person> kid(new Person(name,mom,dad));
        mom->kids.push_back(kid);
        dad->kids.push_back(kid);
        return kid;
    }
    
    
    
    int main(){
        shared_ptr<Person> p = initFamily("nico");
        cout << "nico's family exists
    ";
        cout << "- nico is shared " << p.use_count() << " times
    ";
        cout << "- name of 1st kid of nico's mom: " << p->monther->kids[0]->name << endl;
        p = initFamily("jim");
        cout << "Jim's family exists
    ";
    })

     p = initFamily("jim");
    被赋值前,nico被共享了3次。但是如果释放最末一个指向该家庭的handle(也就是p)---也许是对p指派了一个新Person或者一个nullptr,也许是main()结束离开作用域---总之,没有任何Person被释放,因为它们都仍至少被一个shared pointer指向。于是每个Person的析构函数从未被调用(打印delete name):
    nico's family exists
    - nico shared 3times
    - name of 1st kid of nicos mom: nico
    jim's family exists

    这种情况下使用weak_ptr会带来帮助。

    class Person{
    public:
        string name;
        shared_ptr<Person> monther;
        shared_ptr<Person> father;
        //vector<shared_ptr<Person>> kids;
        vector<weak_ptr<Person>> kids;  //
    
        Person(const string& n, shared_ptr<Person> m = nullptr,
                shared_ptr<Person> f = nullptr) : name(n),monther(m),father(f){        
        }
    
        ~Person(){
            cout << "delete " << name << endl;
        }
    };

    这样就打破了shared_ptr形成的循环,使得在kid到parent方向上用的是shared pointer,parent 到 kids 方向上是weak pointer。

    一旦失去了指向某个家庭的handle,这个家庭中的kid对象也就失去了其最末一个拥有者,导致其父母也都失去了最末拥有者。于是,最初以new建立的所有对象,现在都被delete,因此他们的析构函数都会被调用。

    需要注意到是,使用weak pointer时候,必须改变被指向对象的访问方式,。不应该再调用 p->mother->kids[0]->name,现在必须在式子上加上lock():

    p->mother->kids[0].lock()->name

    这个导致新产生一个“得自于kids容器内含之weak_ptr”的shared_ptr。如果无法进行这样的改动---例如由于对象的最末拥有者也在此时释放了对象---lock()会产生个empty shared_ptr。这种情况下调用* 或者 ->操作符会引发不明确行为。

    如果不确定隐身于weak pointer背后的对象是否存活,有以下三个选择:

    1. 调用expeired() 。他会在weak_ptr不再共享对象时返回true。这等同于检查use_count()是否为0,但是速度较快。

    2. 使用相应的shared_ptr构造函数明确的将weak_ptr转换为一个shared_ptr。如果卑职对象已经不存在,该构造函数抛出一个异常bad_weak_ptr。派生自std::exception

    shared_ptr<stirng> sp(new string("hi"));
    weak_ptr<string> wp = sp;
    shared_ptr<string> p(wp);

    3. 调用use_count(),询问相应对象的拥有者数量。,这通常用于调试,因为并不是总是很有效率。

  • 相关阅读:
    Linux常用命令及示例(全)
    linux下安装配置svn服务器
    Kafka、Redis和其它消息组件比较
    ORACLE定时备份方案
    mysql库表优化实例
    携程apollp快速部署DVE集群
    windows 安装Zookeeper 配置集群
    Ubuntu 18.04 LTS 修改Host,使用Host访问
    PowerShell因为在此系统中禁止执行脚本......
    常用第三方工具的Docker命令
  • 原文地址:https://www.cnblogs.com/gardenofhu/p/9283415.html
Copyright © 2011-2022 走看看