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

    继前一篇:

    shared_pr会出现循环以来造成悬挂指针。另外一个问题则是:必须确保某个对象只被一组shared pointer拥有。示例一个错误代码:

    int * p = new int;
    shared_ptr<int> sp1(p);
    shared_ptr<int> sp2(p);     //ERROR:two shared pointers manage allocated int

    问题在于sp1和sp2都拥有释放权,这会导致两次delete。因此应该直接在创建对象和资源的时候即刻设置smart pointer:

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

    这个问题可能间接发生,根据上一节的例子,准备为Person引入一个成员函数,用来建立“从kid指向parent” 的reference 及其反向reference:

    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));
    kid->setParentsAndTheirKids(mom, dad);
    
    //下面是一个错误的直观版本
    
    class Person{
    public:
        //....
        void setParentAndTheirKids(shared_ptr<Person> m = nullptr,
                                   shared_ptr<Person> f = nullptr) {
             mother = m;
             father = f;
             if(m != nullptr){
                m->kids.push_back(shared_ptr<Perrson>(this));}   //ERROR
             if(f != nllptr){
                f->kids.push_back(shared_ptr<Person>(this));}    //ERROR
        //....
    }
    };

    问题出在this的那个shared pointer 的建立。之所以那么做是为了设置mother 和 father两个成员的kids。但是为了那么做,需要一个shared pointer指向kid,而我们没有 。然而,根据this建立一个新的shared pointer不能解决问题,因为这么一来就是开启了新的拥有者团队(a new group of owners)。

    对付这个问题的办法之一就是,将指向kid的那个shared pointer传递为第三实参。但c++标准库提供了另外一个选项:class std::enable_shared_from_this<>   。

        你可以从class std::enable_shared_from_this<> 派生自己的class,表现出被shared pointer管理的对象,做法是将class 名称当做template是实参传入。

        然后可以使用一个派生的成员函数 shared_from_this() 建立一个源自this的正确shared_ptr。

    class Person : public  std::enable_shared_from_this<Person>{
    public:
        //....
        void setParentAndTheirKids(shared_ptr<Person> m = nullptr,
                                   shared_ptr<Person> f = nullptr) {
             mother = m;
             father = f;
             if(m != nullptr){
                m->kids.push_back(shared_from_this());}   //Ok
             if(f != nllptr){
                f->kids.push_back(shared_from_this());}    //OK
        //....
    }
    };

    但shared_form_this 不能放在构造函数中,因为在Person构造结束之前,shared_ptr本身被存放在Person的基类中,也就是enable_shared_from_this<>内部的一个private成员中。所以,在构造期间,绝对无法建立shared pointer的循环引用。



    get_deleter() 会取得一个pointer to deleter(如果定义deleter的话),要不就取得nullptr。只要shared pointer还拥有那个deleter,该pointer就有效。然而为了取得deleter,你必须以其类型作为template的实参,例如:

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

    需要注意的是,shared pointer 并未提供release() 操作用以放弃拥有权并将对象控制权交给调用者,原因是其他shared pointer可能还拥有该对象。

    更复杂的shared_ptr操作

    aliasing 构造函数指的是“接受一个shared pointer 和一个raw pointer” ,它允许你掌握一个事实(???):某对象拥有另一对象。例如:

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

    类型X的对象拥有成员a,为了产生一个shared pointer指向a,必须保持外环对象(surrounding object)活着,做法是借由aliasing构造函数附加其reference count(被引用次数)。更复杂的例子:指向某个容器元素或者某个shared library symbol。

    需要注意的是,作为一种必然结果,必须确保两对象的寿命相称(match),否则可能发生悬挂指针或者资源泄露。例如:

    shared_ptr<X> sp1(new X);
    shared_ptr<X> sp2(sp1,new X); //ERROR 此X永远不会被delete
    
    sp1.reset(); //deletes firrst X:makes sp1 empty
    shared_ptr<X sp3(sp1,new X); //use_count() == 0,but get() != nullptr

    make_shared() 和allocate——shared() 都用来优化“被共享对象以及其相应之控制区块(如用以维护使用次数)”的创建,。注意: shared_ptr<X> (new X(...)) 执行了二次分配:一次针对X,一次针对shared pointer的控制区块(用以管理使用次数)。如果替换为make_shared<X>(...) 会快速很多,只执行一次分配而且比较安全.allocate_shared() 允许传入自己的allocator作为第一实参。

    线程安全的shared pointer接口

    一般而言,shared pointer并非线程安全,为了避免data race造成不明确的行为,,必须使用mutex或者lock等技术。

    相当于寻常pointer之原子性C-style接口,为shared pointer设计的重载版本允许并发处理shared pointer。注意,并发访问的是pointer而非其指向的值。

    std::shared_ptr<X> global;
    
    void foo(){
        std::shared_ptr<X> local{new X};
        ...
        std::atomic_store(&global,local);
    }




    Class unique_ptr (cppreference.com)

    std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。

    在下列两者之一发生时用关联的删除器释放对象:

    • 销毁了管理的 unique_ptr 对象
    • 通过 operator= 或 reset() 赋值另一指针给管理的 unique_ptr 对象。

    unique_ptr不允许通过赋值语法将一个寻常的指针作为初值,必须直接初始化:

        unique_ptr<int> up = new int;  //ERROR
        unique_ptr<int> up(new int);  //OK

    可以调用release(),获得unique_ptr拥有的对象,于是调用者对该对象负有责任:

    unique_ptr<string> up(new string("nico"));
    ...
    string *sp = up.release();   //获得拥有权

    可以通过bool()操作符检查是否unique pointer拥有对象:

    if(up)
        {
            cout << *up << endl;
        }

    也可以和nullptr比较,以及通过get()成员函数查询unique_ptr内的raw pointer。

    转移unique_ptr的拥有权

    同一个pointer不能同时作为两个unique pointer的初值。

        string* sp = new string("nico");
        unique_ptr<string> up1(sp);
        unique_ptr<string> up2(sp);     //ERROR 运行时错误

    类满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的要求。

        unique_ptr<ClassA> up1(new ClassA);
        //copy
        unique_ptr<ClassA> up2(up1);    //ERROR not possible
        //转移所有权
        unique_ptr<ClassA> up3(std::move(up1)); //OK

    因为unique_ptr是独占式的,所有up2不能成为原对象的另一个拥有者。Assignment操作符也类似。

    需要注意的是,如果要指派新值给unique_ptr,新值也必须是unique_ptr,不可以是寻常pointer:

        std::unique_ptr<ClassA> ptr;
        ptr = new ClassA;       //error
        ptr = unique_ptr<ClassA>(new ClassA);  //OK 删除旧对象并拥有新对象

    (上边这个乍一看跟copy很像,其实不是,因为 new ClassA是新构建的,并不是左值,故不是copy)

    赋值nullptr也是可以的,和调用reset() 效率相同。

    unique_ptr被当作成员

    在class内使用unique_ptr可以避免资源泄露,unique_ptr可以协助避免“对象初始化期间抛出异常而造成的资源泄漏”。对于“拥有多个raw pointer”的class,如果构造期间第一个new成功而第二个失败,就可能导致资源泄漏,例如:

    class ClassB {
    private:
        ClassA * ptr1;
        ClassA* ptr2;
    public:
        //构造函数,初始化指针,第二个new可能失败,造成资源泄漏
        ClassB(int val1, int val2) : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {}
        //复制构造函数,第二个new可能分配失败
        ClassB(const ClassB& x) :ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}
        //赋值操作符
        const ClassB& operator= (const ClassB& x) {
            *ptr1 = *x.ptr1;
            *ptr2 = *x.ptr2;
            return *this;
        }
        ~ClassB() {
            delete ptr1;
            delete ptr2;
        }
    };
    class ClassB {
    private:
        unique_ptr<ClassA> ptr1;
        unique_ptr<ClassA> ptr2;
    public:
        //构造函数,初始化unique_ptr指针,不会造成资源泄漏
        ClassB(int val1, int val2) : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {}
        //复制构造函数,
        ClassB(const ClassB& x) :ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}
        //赋值操作符
        const ClassB& operator= (const ClassB& x) {
            *ptr1 = *x.ptr1;
            *ptr2 = *x.ptr2;
            return *this;
        }
      //不需要析构函数 };

    上边使用unique_ptr不需要写析构函数,但必须写copy构造函数和赋值操作符构造函数,因为默认版本会拷贝或者赋值成员,但这对于unique_ptr是不可能的,如果你没有自己提供,ClassB将只提供move语义。

    对于Array

    对于array有特殊的规定。就因为释放对象应该是delete[]而不是delete。C++ std提供了class unique_ptr的一个偏特化版本处理array:

    unique_ptr<string> up(new string[10]);  //ERROR
    unique_ptr<string[]> up(new string[10]);  //OK
    ...
    cout << *up << endl;  //ERROR
    cout << up[0] << endl;  //OK

    注意,对于array来讲,这里* 和->不再提供,只能用操作符[]。

    其他资源的dleter

    当指向的资源用delete或者delete[]释放满足不了需求时,需要自定义deleter。此处的deleter定义方式不同于shared_ptr。必须指明deleter的类型作为第二个template实参。该类型可能是函数引用,或者函数指针或者函数对象(function object,也叫仿函数)。如果是函数对象,其function call操作符 () 应该接受一个 “指向对象”的pointer。举例而言:

    class ClassADeleter {
    public:
        void operator () (ClassA* p) {
            cout << "call delete for ClassA object" << endl;
            delete p;
        }
    };
    
    unique_ptr<ClassA, ClassADeleter> up(new ClassA);

    如果你给的是个函数或lambda,你必须声明deleter的类型为 void(*)(T*) 或者 function<void(T*)>,要不就使用decltype。例如,若要为一个array of int 指定自己的deleter,并以lambda形式呈现,应该如下:

    std::unique_ptr<int, void(*)(int*)> up(new int[10],
        [](int* p) {
        //...
        delete[] p;
    });
    
    std::unique_ptr<int, std::function<void(int *)>> up1(new int[10],
        [](int* p) {
        //...
        delete[] p;
    });
    
    auto l = [](int *p) {
        //...
        delete[] p;
    };
    
    std::unique_ptr<int, decltype(l)> up3(new int[10], l );

    为了避免“传递function pointer 或者 lambda时必须指明deleter的类型”,可以使用 alias template这是自c11的语言特性:

    template <typename T>
    using uniquePtr = std::unique_ptr<T, void(*)(T*)>;
    //...
    uniquePtr<int> up(new int[10], [](int* p)(int* p) {
        //...
        delete[] p;
    });

    下面是个完整的例子 :

    #include<memory>
    #include<iostream>
    #include<dirent.h>
    #include<string>
    #include<cstring>
    #include<cerrno>
    using namespace std;
    
    class DirDirCloser{
    public:
        void operator() (DIR* dp){
            if(closedir(dp) != 0){
                std::cerr << "Failed closedir()" << std::endl;
            }
        }
    };
    
    int main(){
        //open current dir
        unique_ptr<DIR,DirDirCloser> pDir(opendir("."));
        //...
        //process each dir entry;
        struct dirent 8 dp;
        while((dp = readdir(pDir.get())) != nullptr){
            string filename(dp->d_name);
            cout << "process " << filename << endl;
        }
    }

    如果必须昂处理closedir的返回值,可以直接传递closedir() 成为一个function pointer,并明确指出deleter是个function  pointer。但是务必晓得,下面这个经常被推荐的声明式子并不保证可移植:

    unique_ptr<DIR, int(*)(DIR*)> pDir(opendir("."),closedir);

    因为closedir具备extern “C” linkage,所以在C++代码中并不能保证被转换为int(*)(DIR*)。下面的方式具备可移植性,其需要一个中介类型定义式如下:

    extern "C" typedef int(*DIRDeleter)(DIR*);
    unique_ptr<DIR, DIRDeleter> pDir(open("."),closedir);

  • 相关阅读:
    笔试材料收集(二)——用OPENGL搞个冒泡排序,原创_!
    cocos2dx andoroid切换后台后资源重载
    ipa命令行打包命令
    SceneManager
    ios上遇到过的问题集及解决方法(1)
    google inapp billing
    Unity Editor学习IHasCustomMenu
    cocos2dx如果更好地使用第三库
    cocos2dhtml环境布暑
    ios中,常用的一些调试命令
  • 原文地址:https://www.cnblogs.com/gardenofhu/p/9288400.html
Copyright © 2011-2022 走看看