智能指针 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(),询问相应对象的拥有者数量。,这通常用于调试,因为并不是总是很有效率。

原文地址:https://www.cnblogs.com/gardenofhu/p/9283415.html