C++动态内存与智能指针

静态内存用来保存局部 static 对象、类 static 数据成员 以及任何定义在函数之外的变量。栈内存用来存储定义在函数内部的非 static 对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static 对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存周期由程序来控制,即当动态对象不再使用时,我们的代码必须显示的销毁它们。

动态内存与智能指针:

c++中内存时通过一对运算符来完成的:new,在动态内存中为对象分配内存空间并返回一个指向该对象的指针,我们可以选择对象进行初始化;delete,接受一个动态对象的指针,销毁该对象并释放与之关联的内存。

c++11标准库中提供了两种智能指针类型来管理动态内存对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr 允许多个指针指向同一个对象;unique_ptr 则 “独占” 所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一个弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中

shared_ptr 类:

智能指针也是模板:

#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<string > p1;//p可以指向string对象,没有指向构造函数,所以进行默认初始化,指向一个空的string
    shared_ptr<list<string>> p2;//p1可以指向list对象
    if(p1 && p1->empty()) *p1 = "hi";//如何通过动态指针得到所指向的对象?
    return 0;
}

shared_ptr 和 unique_ptr 都支持的操作:

shared_ptr sp, unique_ptr up空智能指针,可以指向类型为 T 的对象
p将 p 用作一个条件判断,若 p 指向一个对象,则为 true
*p解引用 p,获得它指向的对象
p->mem等价于 (*p).mem
p.get()返回 p 中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。通常用于得到 p 中保存的传统风格指针,因为用shared_ptr直接给普通指针赋值或初始化是非法的
p.swap(q) ,swap(p, q)交换 p 和 q 中的指针

shared_ptr 独有的操作:

make_shared(args)返回一个 shared_ptr,指向一个动态分配的类型为 T 的对象。使用 args 初始化此对象
shared_ptrp(q)p 是 shared_ptr q 的拷贝:此操作会递增 q 中的计数器。q 中的指针必须能转换为 T*
p = qp 和 q 都是 shared_ptr,所保存的指针必须能相互转换。此操作会递减 p 的引用计数,递增 q 的引用计数;若 p 的引用计数变为 0,则将其管理的原内存释放
p.unique()若 p.use_count() 为 1,返回 true,否则返回 false
p.use_count()返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试

make_shared 函数:
最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数,我们应该尽量适用

#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<int> p1=make_shared<int>(42);
    cout<<*p1<<endl;

    shared_ptr<string> p2=make_shared<string>(9,'c');
    cout<<*p2<<endl;

    shared_ptr<int> p3=make_shared<int>();
    cout<<*p3<<endl;//默认初始化(值初始化)
}

注意:使用 make_shared 时,必须指定要创建的对象类型。定义方式与模板类相同,在函数名后跟一个尖括号,在其中给出类型

类似顺序容器的 emplace 成员,make_shared 用其参数来构造给定类型的对象。因此给的参数必须符合对应类型的构造规则
用 auto实现以上程序

#include<bits/stdc++.h>
using namespace std;
int main(){
    auto p1=make_shared<int>(42);
    cout<<*p1<<endl;

    auto p2=make_shared<string>(9,'c');
    cout<<*p2<<endl;
    
    auto p3=make_shared<int>();
    cout<<*p3<<endl;//默认初始化(值初始化)
    return 0;
}

拷贝shared_ptr指针

#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<int> p=make_shared<int>(26);
    shared_ptr<int> q(p);
    cout<<q.use_count()<<endl;
    cout<<q.use_count()<<endl;
    shared_ptr<int> t=q;
    cout<<t.use_count()<<endl;
    cout<<p.use_count()<<endl;
    return 0;
}

我们可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个 shared_ptr,计数器都会递增。如,当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给另一个函数以及作为函数的返回值时,它所关联的计数器都会递增。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(如,一个局部的 shared_ptr 离开其作用域)时,计数器都会递减。一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象:
shared_ptr 自动销毁所管理的对象:

当指向对象的最后一个 shared_ptr 被销毁时,shared_ptr 会自动通过析构函数销毁此对象 。由于在最后一个 shared_ptr 销毁之前内存都不会释放,保证 shared_ptr 在无用之后不再保存就十分·重要了。如果忘记销毁程序不再需要的 shared_ptr,程序仍会正确执行,但会浪费内存。shared_ptr 在无用之后仍然保留的的一种可能情况是,你将 shared_ptr 存放在一个容器中,随后重排了容器,从而不再需要某些元素。这种情况下,你应该确保用 erase 删除那些不再需要的 shared_ptr 元素

使用了动态生存期的资源的类:
程序使用动态内存处于以下三种原因之一:
1.程序不知道自己需要使用多少对象

2.程序不知道所需对象的准确类型

3.程序需要在多个对象间共享数据

我们来分析一波第 3 种情况:
我们之前用过的类中,分配的资源都与对应对象生存期一致。如:每个 vector “拥有” 其自己的元素。当我们拷贝一个 vector 时,原 vector 和 副本 vector 中的元素是相互分离的:

但某些类分配的资源具有与原对象相独立的生存期。如,假定我们希望定义一个名为 Blod 的类,保存一组元素。与容器不同,我们希望 Blod 对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个 Blod 时,原 Blod 对象及其拷贝应该引用相同的底层元素。

通常,如果两个对象共享底层数据,当某个对象被销毁时,我们不能单方面销毁底层数据:
定义一个管理 string 的 Blod 类,命名为 strBlod:

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class strBlod{
public:
    typedef std::vector<std::string>::size_type size_type;
    strBlod();
    strBlod(std::initializer_list<std::string> il);
    ~strBlod();

    size_type size() const {
        return data->size();
    }

    bool empty() const {
        return data->empty();
    }

    //添加和删除元素
    void push_back(const std::string &t){
        data->push_back(t);
    }
    void pop_back();

    //元素访问
    std::string& front();
    std::string& front() const;

    std::string& back();
    std::string& back() const;

    int get_use_count() const{//得到shared_ptr成员data的引用计数
        return data.use_count();
    }

private:
    std::shared_ptr<std::vector<std::string>> data;

    //如果data[i]不合法,抛出一个异常
    void check(size_type i, const std::string &msg) const;

    std::string& front_display() const;
    std::string& back_display() const;
    
};

strBlod::strBlod() : data(make_shared<vector<string>>()) {}//默认构造函数
strBlod::strBlod(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
strBlod::~strBlod() {}

void strBlod::check(size_type i, const string &msg) const {
    if(i >= data->size()) throw out_of_range(msg);
}

string& strBlod::front(){
    return front_display();
}

string& strBlod::front() const{
    return front_display();
}

string& strBlod::front_display() const{
    //如果vector为空,check会抛出一个异常
    check(0, "front on empty strBlod!");
    return data->front();
}

string& strBlod::back(){
    return back_display();
}

string& strBlod::back() const{
    return back_display();
}

string& strBlod::back_display() const{
    check(0, "back on empty strBlod!");
    return data->back();
}

void strBlod::pop_back(){
    check(0, "pop_back on empty strBlod!");
    data->pop_back();
}

int main(void){
    strBlod b1;//b1执行默认构造
    cout << b1.get_use_count() << endl;//1

    {
        strBlod b2({"hello", "word", "!"});
        cout << b2.get_use_count() << endl;//1
        b1 = b2;//b1和b2共享元素
        cout << b1.get_use_count() << endl;//2
        cout << b1.get_use_count() << endl;//2
        // 当我们拷贝、赋值或销毁一个 strBlod 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁
    }
    // string s = b2.front();//错误,离开了b2的作用域,b2被销毁了
    //b1指向原本由b2创建的元素

    cout << b1.get_use_count() << endl;//1
    string s = b1.front();
    cout << s << endl;//hello

    return 0;
}//离开b1的作用域,b1被销毁
//由strBlod构造函数分配的vector已经没有strBlod对象指向它,此时被自动销毁

注意:当我们拷贝、赋值或销毁一个 strBlod 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁
如果一个 shared_ptr 的引用计数变为 0,它所指像的对象会被自动销毁。因此,对于由 strBlod 构造函数分配的 vector,当最后一个指向它的 strBlod 对象被销毁时,它也会随之被自动销毁

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class strBlod{
public:
    typedef std::vector<std::string>::size_type size_type;
    strBlod();
    strBlod(std::initializer_list<std::string> il);
    ~strBlod();

    size_type size() const {
        return data->size();
    }

    bool empty() const {
        return data->empty();
    }

    //添加和删除元素
    void push_back(const std::string &t){
        data->push_back(t);
    }
    void pop_back();

    //元素访问
    std::string& front();
    std::string& front() const;

    std::string& back();
    std::string& back() const;

    int get_use_count() const{//得到shared_ptr成员data的引用计数
        return data.use_count();
    }

private:
    std::shared_ptr<std::vector<std::string>> data;

    //如果data[i]不合法,抛出一个异常
    void check(size_type i, const std::string &msg) const;

    std::string& front_display() const;
    std::string& back_display() const;
    
};

strBlod::strBlod() : data(make_shared<vector<string>>()) {}//默认构造函数
strBlod::strBlod(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
strBlod::~strBlod() {}

void strBlod::check(size_type i, const string &msg) const {
    if(i >= data->size()) throw out_of_range(msg);
}

string& strBlod::front(){
    return front_display();
}

string& strBlod::front() const{
    return front_display();
}

string& strBlod::front_display() const{
    //如果vector为空,check会抛出一个异常
    check(0, "front on empty strBlod!");
    return data->front();
}

string& strBlod::back(){
    return back_display();
}

string& strBlod::back() const{
    return back_display();
}

string& strBlod::back_display() const{
    check(0, "back on empty strBlod!");
    return data->back();
}

void strBlod::pop_back(){
    check(0, "pop_back on empty strBlod!");
    data->pop_back();
}

int main(void){
    strBlod b1;//b1执行默认构造
    cout << b1.get_use_count() << endl;//1

    {
        strBlod b2({"hello", "word", "!"});
        cout << b2.get_use_count() << endl;//1
        b1 = b2;//b1和b2共享元素
        cout << b1.get_use_count() << endl;//2
        cout << b1.get_use_count() << endl;//2
        // 当我们拷贝、赋值或销毁一个 strBlod 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁
    }
    // string s = b2.front();//错误,离开了b2的作用域,b2被销毁了
    //b1指向原本由b2创建的元素

    cout << b1.get_use_count() << endl;//1
    string s = b1.front();
    cout << s << endl;//hello

    return 0;
}//离开b1的作用域,b1被销毁
//由strBlod构造函数分配的vector已经没有strBlod对象指向它,此时被自动销毁
直接管理内存:

使用 new 动态分配和初始化对象:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int *p1=new int;//p1指向一个未初始化的对象,
    cout<<*p1<<endl;//默认情况下,内置类型默认分配的内存执行默认初始化

    string *s1=new string;//对于类类型执行构造函数初始化
    cout<<*s1<<endl;//空的字符串

    //在类型后跟一个括号是默认初始化
    int *p2=new int(1024);//构造初始化
    cout<<*p2<<endl;

    string *s2=new string(10,'c');//构造初始化
    cout<<*s2<<endl;

    //使用auto进行初始化
    int obj1=1024;
    auto p3=new auto(obj1);
    cout<<*p3<<endl;

    string obj2("使用auto进行初始化");
    auto    *s3=new auto(obj2);
    cout<<*s3<<endl;

    int obj3=1,obj4=2,obj5=3;
//    auto *t=new auto{obj3,obj4,obj5};//不能用花括号赋值

    //使用const动态分配对象
    const int *p4=new const int(1024);
    cout<<*p4<<endl;//动态分配一个const对象。必须显式初始化

    return 0;
}

注意:在自由空间分配的内存是无名的,因此 new 无法为其分配的对象命名,而是返回一个指向该对象的指针

默认情况下,动态内存分配的对象是默认初始化的,即,内置类型或组合类型的对象的值将是未定义的(随机值),而类类型对象将用默认构造函数进行初始化

对于定义了自己的构造函数的类类型来说,要求值初始化是没有意义的,不管采用什么形式,对象都会通过默认构造函数类初始化

对于内置类型,应该尽量使用值初始化来代替默认初始化,因为其默认初始化是未定义的

如果我们提供了一个括号包围的初始化器,就可以使用 auto 从此初始化器来推断类型。但是由于编译器要用初始化器的类型来推断分配的类型,只有当括号中仅有单一初始化器是才可以使用 auto

动态分配 const 对象时对于没有默认构造函数的类类型,必须显示初始化

内存耗尽:

当一个程序用完了自由空间,那么它的内存就会耗尽,new表达式失败,所以就会抛出bad_alloc异常
我们可以改变使用 new 的方式来阻止它抛出异常:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int p1=new int();//
    int p1=new (nothrow)int;//这样就不会抛出异常了,但是必须保证这样做程序是正确的
    return 0;
}

释放动态内存:

#include<bits/stdc++.h>
using namespace std;
int main(){
    //delete指向一个指针,指针指向对象,这个指针必须指向对象或者是一个空指针。
    int i,*pi=&i;
//    delete i;//不是一个指针。报错
    delete pi;//编译通过,但是指针指向一个局部变量,行为未定义

    int *p=new int(1024),*p1=p;
    delete p;//释放动态内存
    cout<<"***"<<endl;
//    delete p1;//多次释放行为未定义。
    const int *p2=new const int(11);
    delete p2;//正确释放一个const动态内存
    return 0;
}
动态内存管理常见错误:

1.忘记 delete 内存,导致内存泄漏

2.使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误

3.同一块内存释放多次

delete 之后重置指针:

在 delete 之后,指针就变成了空悬指针。可以在 delete 之后将 nullptr 赋予指针,这样就清楚地指出指针不指向任何对象。但是,动态内存可能有多个指针指向相同的内存。在 delete 之后重置指针的方法只对这个指针有效,对其它任何仍指向(已释放的)内存的指针是没有作用的:

#include<bits/stdc++.h>
using namespace std;
int main(){
    //delete指向一个指针,指针指向对象,这个指针必须指向对象或者是一个空指针。
    int i,*pi=&i;
//    delete i;//不是一个指针。报错
    delete pi;//编译通过,但是指针指向一个局部变量,行为未定义
    pi=nullptr;

    int *p=new int(1024),*p1=p;
    delete p;//释放动态内存
    cout<<"***"<<endl;
//    delete p1;//多次释放行为未定义。
    const int *p2=new const int(11);
    delete p2;//正确释放一个const动态内存
    p2=nullptr;
    return 0;
}
shared_ptr 和 new 结合使用:
#include<bits/stdc++.h>
using namespace std;
shared_ptr<int> clone(int p){
    return new int(p);//因为是explicit的构造函数,所以创建临时变量的时候遇到了错误。
    return shared_ptr<int>(new int (p));//正确,显式转换
}
int main(){
    //不初始化智能指针,智能指针就被初始化为空指针
    //但是可以使用内置指针初始化智能指针
    shared_ptr<double> p1;
    shared_ptr<double> p2(new int(1024));
//    shared_ptr<double> p3=new int(1024);//拷贝构造参数是explicit的,不能隐式转换一个内置指针为智能指针

    return 0;
}

注意:接受指针参数的智能指针构造函数是 explicit 的,我们不能将一个内置指针隐式转化一个智能指针,必须使用直接初始化形式来初始化一个智能指针

智能指针默认使用 delete 释放它所关联的对象,默认情况下一个用来初始化智能指针的普通指针必须指向动态内存。

我们也可以将智能指针绑定到一个指向其它类型的资源的指针上,但是为了这么做,必须提供自己的操作来替代 delete

定义和改变 shared_ptr 的其它方法:
shared_ptr p(q)p 管理内置指针 q 所指向的对象:q 必须指向 new 分配的内存且能够转换为 T* 类型
shared_ptr p(u)p 从 unique_ptr u 那里接管了对象的所有权:将 u 置为空
shared_ptr p(q, d)p 接管了内置指针 q 所指向的对象的所有权。q 必须能转为 T* 类型。p 将使用可调用对象 d 来代替 delete
shared_ptr p(p2, d)p 是 shared_ptr p2 的拷贝,且用可调用对象 d 来替代 delete
p.reset()   p.reset(q)  p.reset(q, d)若 p 是唯一指向其对象的 shared_ptr,reset 会释放此对象。若传递了可选参数指针 q,会令 p 指向 q,否则会将 p 置为空。若还传递了可选参数d,将会调用 d 而不是 delete 来释放 q

不要混合使用普通指针和智能指针:

#include<bits/stdc++.h>
using namespace std;
void process(shared_ptr<int> ptr){}//离开作用域,ptr计数器减一
int main(){//不要混合使用隐式指针和智能指针
    shared_ptr<int> p1(new int(42));
    process(p1);//在传参的时候,计数器加一,在退出的时候,计数器减一,对象仍旧存在
    cout<<*p1;

    int i=9,*x=&i;
    process(shared_ptr<int>(x));//错误,此时x已经被释放,成为一个空悬指针
    cout<<i<<endl;
    return 0;
}

注意:当将一个 shared_ptr 绑定到一个普通指针时,我们就将内存的管理交给了这个 shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问 shared_ptr 所指向的内存了
显示 / 隐式转换时即创建了一个对应的临时变量
离开创建临时变量的表达式时,该表达式中创建的临时变量会被销毁.

不要使用 get 初始化另一个智能指针或为智能指针赋值:

注意:get 通常用于将 shared_ptr 中保存的值赋给普通指针,因为用 shared_ptr 直接给普通指针赋值或初始化是非法的,反之亦然

只有在确定代码不会 delete 指针的情况下才能用 get
永远不要用 get 初始化另一个智能指针或为另一个智能指针赋值
两个相互独立但指向相同的内存的 shared_ptr 各自的引用计数都是 1,着意味着当其中一个自动销毁但另一个没被销毁时,没被销毁的 shared_ptr 会成为空悬指针

使用get返回一个普通指针,不能使用这个普通指针再给一个智能指针赋值

#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<int> p(new int(42));
    int *q=p.get();
    cout<<q<<endl;
    {
        shared_ptr<int> p1(q);
    }//当这个新的作用域结束之后,q指向的内存会被释放,
    cout<<*p<<endl;//此时p是一个空悬指针。
    return 0;
}

reset()操作

#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<int> p=make_shared<int>(1024);
    //p=new int(0);错误,因为explicit的构造函数
    p.reset(new int (0));
    (*p)++;
    cout<<*p<<endl;//将p指向了0的动态内存对象

    if(!p.unique()) p.reset(new int (*p));
    *p+=1;
    cout<<*p<<endl;
    return 0;
}
智能指针和异常:
void f(){
    int ip=new int(5);
    /**///这里发生了异常,导致程序结束
    delete ip;//那些内存永远也不会被释放了。但是如果ip是一个智能指针,不论异常还是作用域结束,内存都会被释放
}

注意:使用内置指针管理内存,且在 new 之后再对应的 delete 之前发生了异常,则内存不会被释放。但如果上例中 ip 是一个 shared_ptr,则仍会在其作用域结束时正常释放

删除器:

当一个 shared_ptr 管理的对象被释放时,默认是进行 delete 操作,而 delete 操作是调用对应对象的析构函数来完成的。这意味这对于分配了资源而又没有定义析构函数的类,我们需要定义一个函数来代替 delete 完成对 shared_ptr 中保存的指针进行释放操作,即删除器:

#include <iostream>
using namespace std;

struct destination;//表示我们正在连接什么
struct connection;//使用连接所需的信息
connection connect(destination*);//打开连接
void disconnect(connection);//关闭给定的连接

void end_connection(connection *p){//删除器
    disconnect(*p);
}

void f(destination &d /*其它参数*/){
    connection c = connect(&d);
    shared_ptr<connection> p(&c, end_connection);
    //使用连接
    //当f推出时,connection会被正确关闭
}

注意:当 p 被销毁时,它不会对自己保存的指针执行 delete,而是直接调用 end_connection

智能指针的陷阱:

智能指针的安全性是建立在正确使用的前提下的,我们必须坚持一些基本规范:

不使用相同的内置指针初始化(或 reset)多个智能指针。这会造成前面我们提到的相互独立的 shared_ptr 之间共享相同地址的问题

不 delete get() 返回的指针

不适用 get() 初始化或 reset 另一个智能指针

如果使用 get() 返回的指针,记住当最后一个对应的 shared_ptr 销毁后,之前 get() 返回的的指针就无效了
如果使用智能指针管理的不是 new 分配的内存,必须传给它一个删除器

unique_ptr:

与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向给定的对象。当 unique 被销毁时,它所指向的对象也被销毁

unique_ptr 操作:

unique_ptr u1空 unique_ptr,可以指向一个类型为 T 的对象。u1 会使用 delete 来释放它的
unique_ptr<T, D> u2指针;u2 会使用一个类型为 D 的可调用对象来释放它的指针,初始化 u2 时除了需要一个能转化成 T* 类型的对象外还需要一个 D 类型的对象来代替delete
unique_ptr<T, D> u(d)空 unique_ptr,指向类型为 T 的对象,用类型为 D 的对象 d 代替 delete
u = nullptr释放 u 指向的对象,将 u 置为空
u.release()u 放弃对指针的控制权,返回指针,并将 u 置为空
u.reset()释放 u 指向的对象
u.reset(q)如果提供了内置指针 q,令 u 指向这个对象,否则将 u 置为空u.reset(nullptr)

初始化 unique_ptr 必须采用直接初始化形式:

unique_ptr p1;//可以指向一个double的unique_ptr
unique_ptr p2(new int(42));//p2指向一个值为42的int

#include<bits/stdc++.h>
using namespace std;
int main(){
    unique_ptr<string> p1(new string("string"));
//    unique_ptr<string> p2=p1;//错误,不能使用这个赋值
    unique_ptr<string> p2;
    p2=p1;//赋值运算符函数是=delete的,所以不能使用
    return 0;
}
```       
unique_ptr 是通过 release 或 reset 将指针的所有权从一个 (const) unique_ptr 转移给另一个 unique_ptr 的:     
```cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
    unique_ptr<string> p1(new string("abc"));//必须采取直接初始化的方式
    unique_ptr<string> p2(p1.release());//可以通过release来转移控制权.release放弃控制权,然后将p1置空。
    unique_ptr<string> p3(p1.release());//报错,只能采取直接初始化的方式
    p1.reset(p2.release());
    cout<<*p1<<endl;
    return 0;
}

注意:如果 unique_ptr 不为空,则调用 reset 会释放其原来所指的内存。而 release 则不会释放 unique_ptr 原来所指的内存。调用 release 会切断 unique_ptr 和它原来所指对象间的联系,如果我们不是用另一个智能指针来保存 release 返回的指针,则我们的程序就要负责资源的释放:

p2.release();//错误,p2 不会释放内存,而且我们丢失了指针

auto p = p2.release();//正确,但我们必须记得 delete§

传递 unique_ptr 参数和返回 unique_ptr:

不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要销毁的 unique_ptr:

从函数返回一个 unique_ptr:

#include<bits/stdc++.h>
using namespace std;
unique_ptr<int> f(int p){
    return unique_ptr<int>(new int(p));
}
unique_ptr<int> f1(int p){
    unique_ptr<int>ret(new int(p));
    return ret;
}

注意:这两段代码,编译器都知道要返回的对象将要被销毁。在此种情况下,编译器执行一种特殊的 “拷贝” (移动构造函数)

向 unique_ptr 传递删除器:

类似 shared_ptr,unique_ptr 默认情况下用 delete 释放它指向的对象。同样的,我们可以重载一个 unique_ptr 中默认的删除器:

unique_ptr<objT, delT> p(new objT, fcn);
//p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象 fnc 来释放 objT 对象

类似于关联容器中重载比较运算函数,等于判断函数,哈希函数等

void f(destination &d /*其它需要的参数*/){
    connection c = connect(&d);//打开连接
    //当p被销毁时,连接会自动关闭
    unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
    //decltype只是返回了一个函数类型,必须添加*来指出我们正在使用一个函数指针
    //使用连接
    //当f退出时(即使是由于异常而退出),connection会被正常关闭
}

weak_ptr:

weak_ptr 指向一个 shared_ptr 管理的对象。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,即便有 weak_ptr 指向对象,对象也还是会被释放的

weak_ptr 的操作:

weak_ptr w空 weak_ptr 可以指向类型为 T 的对象
weak_ptr w(sp)与 shared_str sp 指向相同对象的 weak_ptr。T 必须能转化为 sp 指向的对象的类型
w = pp 可以是一个 shared_ptr 或一个 weak_ptr。赋值后 w 与 p 共享对象
w.reset()将 w 置为空
w.use_count()与 w 共享对象的 shared_ptr 的数量
w.expired()若 w.use_count 为 0,返回 true,否则返回 false
w.lock()如果 w.expired() 为 true,返回一个空 shared_ptr,否则返回一个指向 w 的对象的 shared_ptr

创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它:

#include<bits/stdc++.h>
using namespace std;
int main(){
    //weak_ptr不控制所指对象生存周期的函数、
    auto p=make_shared<int>(42);
    weak_ptr<int> p1(p);//创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它:,而不能用其他的类型来初始化
    cout<<p1.use_count()<<endl;;//返回有多少个shared指指针指向这个对象
    weak_ptr<int> p2=p,p3=p1;//赋值初始化

    int *cnt=new int(1024);
//    weak_ptr<int> b=cnt;//错误,只能shared_ptr赋值
//    weak_ptr<int> b(cnt);
    return 0;
}

使用 weak_ptr 时,由于我们不确定其所指对象是否存在,要先调用 lock 函数检查一下其所指对象是否存在


#include<bits/stdc++.h>
using namespace std;
int main(){
    shared_ptr<int> q(new int(45));
    weak_ptr<int> p(q);

    cout<<p.lock()<<endl;
    if(shared_ptr<int> t=p.lock()){//如果nq不为空,则条件成立
        cout<<"yes"<<endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值