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

个人理解:所谓智能指针就是为了防止出现内存泄露,出现野指针等操作的而定义的一种类型。
在标准库当中有两种类型的智能指针

  • shared_ptr 允许多个指针指向同一个对象
  • unique_ptr 独占所指向的对象

另外还有weak_ptr的伴随类和auto_ptr,指向shared_ptr所管理的对象。auto_ptr是C++98版本中的内容,C++11中已经不再使用。
头文件名为 memory

使用动态内存的原因:

  1. 程序不知道自己需要使用多少对象
  2. 程序不知道对象的准确类型
  3. 程序要在多个对象间共享数据

问题

c++ primer第四版 p420页上面的带有指针成员的类
(有的编译器有优化,对指针值的拷贝构造函数会考虑到悬垂指针的情况)

class HasPtr
{
public:
    HasPtr(int *p,int i):ptr(p),val(i){}
    int *get_ptr() const { return ptr; }
    int get_int() const { return val; }
    void get_ptr(int *p) { ptr=p; }
    void set_int(int i) { val=i ;}
    int get_ptr_val() const { return *ptr; }
    void set_ptr_val(int val) const { *ptr=val; }
private:
    int *ptr;
    int val;
};

int main()
{
    ios::sync_with_stdio(false);
    int *ip=new int(42);
    HasPtr ptr(ip,10);
    delete ip;
    ptr.set_ptr_val(0);
    return 0;
}

上面的代码当中,ip和ptr中的指针指向同一个对象。删除该对象时,ptr中的指针不再指向有效对象。然而,没有办法得知对象已经不在存在了。(p421)
其中的HasPtr中的val只表示HasPtr这个类中包含除了指针类型成员以外,还包含一个指针成员,所以val的值和智能指针这部分没有联系。

定义智能指针

智能指针将保证在撤销指向对象的最后一个HasPtr对象时删除对象。为此,添加一个析构函数用来删除指针,删除标准为指向该对象最后一个指针也被撤销,那么删除该对象,以便回收内存。

方法:
引入计数器,每次创建类的新对象时,初始化指针并将计数置为1,当对象作为另一个对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。

代码中删除了val这个成员,代码来自c++ primer 第四版

#include <bits/stdc++.h>
using namespace std;
class HasPtr;

class U_Ptr
{
    friend class HasPtr;
    int *ip;
    size_t use;
    U_Ptr(int *p):ip(p),use(1){}
    ~U_Ptr(){ delete ip; }
};

class HasPtr
{
public:
    HasPtr(int *p): ptr(new U_Ptr(p)){}
    HasPtr(const HasPtr &orig):ptr(orig.ptr){ ++ptr->use; }
    HasPtr& operator = (const HasPtr&);
    int get_ptr() { return *(ptr->ip); }
    int get_use() { return ptr->use; }
    ~HasPtr() { if(--ptr->use==0) delete ptr; }
private:
    U_Ptr *ptr;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    ++rhs.ptr->use;
    if(--ptr->use==0)
        delete ptr;
    ptr = rhs.ptr;
    return *this;
}

int main()
{
    ios::sync_with_stdio(false);
    int* obj=new int(1);//注意不要delete obj
    HasPtr *hp1=new HasPtr(obj);
    HasPtr hp2=*hp1;//赋值

    cout<<hp1->get_use()<<endl;//输出2
    cout<<hp2.get_use()<<endl;//输出2

    delete hp1;
    cout<<hp2.get_use()<<endl;//输出1

    return 0;
}

上面代码的图
这里写图片描述

标准库中的智能指针

在boost库中和c++11的memory库当中都有对智能指针的实现,下面记录和整理C++11的memory库中的智能指针的简单用法。

shared_ptr类

shared_ptr操作

  1. use_count 返回与p共享对象的智能指针的数量
  2. unique 判断use_count是否为1,如果为1则返回true
  3. get 返回保存的指针
#include <bits/stdc++.h>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);

    string *s=new string("abc");

    shared_ptr<string> p1(s);

    cout<<*p1<<endl;//解引用

    cout<<p1.unique()<<endl;//是否只有一个指针指向目标内存

    cout<<p1.use_count()<<endl;//输出与p1共享对象的智能指针的数量

    shared_ptr<string> p2(p1);

    cout<<p1.unique()<<endl;//输出0

    cout<<p1.use_count()<<endl;//输出2

    cout<<*(p1.get())<<endl;//返回p1中保存的指针

    delete s;

    return 0;
}

是用make_shared函数构造给定类型的对象。

shared_ptr<int> pi=make_shared<int>(100);

cout<<*make_shared<int>(100)<<endl;//可以直接输出试试

shared_ptr释放内存

  • 当指向一个对象的最后一个shared_ptr被销毁时shared_ptr会自动销毁对象。
  • 当动态对象不再被使用时,shared_ptr会自动释放动态对象。(例如在局部变量当中使用指针)

举个局部变量造成内存泄露的例子
如果程序这样写

int* P_factory(int x)
{
    return new int[x];
}

void p_test(int x)
{
    P_factory(x);//分配的内存在局部变量当中丢失(也就是没有指针指向这块内存,从而造成内存泄露)
}
int main()
{
    ios::sync_with_stdio(false);
    for(int i=1;i<=100000;i++)
        p_test(10000);//boom!
    return 0;
}

但是如果用了shared_ptr

shared_ptr<int> factory(int x)
{
    return make_shared<int>(x);
}
void test(int x)
{
    factory(x);//自动回收内存
}
int main()
{
    ios::sync_with_stdio(false);
    for(int i=1;i<=100000;i++)
        test(10000);//什么也不会发生
    return 0;
}

shared_ptr与new结合使用:

使用shared_ptr最需要注意的就是不要和内置指针(普通指针)混用以及相互关联,也不要使用get初始化另一个智能指针或者为智能指针赋值!

接受指针参数的智能指针构造函数是explict的

shared_ptr<int> clone(int p)
{
    return new int(p);//错误,无法隐式转换
}
shared_ptr<int> clone(int p)
{
    return shared_ptr<int>(new int(p));
}
shared_ptr<int> p1 = new int(1024);//错误
shared_ptr<int> p2(new int(1024));

reset操作:

如果当前指针的unique()值为1,那么调用reset函数重新指向一个对象时,先释放之前的内存。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);

    shared_ptr<int> sp(new int(100));
    shared_ptr<int> sp1(sp);

    cout<<sp.use_count()<<" "<<sp1.use_count()<<endl;//2 2

    sp.reset(new int(10));
    cout<<sp.use_count()<<" "<<sp1.use_count()<<endl;//1 1
    return 0;
}

unique_ptr类
顾名思义,只有一个指针指向当前对象。unique_ptr的生命周期为从创建开始到离开作用域,如果离开作用域时未释放指针,系统会自动回收内存,释放指针。所以,unique_ptr不支持拷贝与赋值操作。

unique_ptr操作

  1. reset 释放指针所指向的对象
  2. get 获得指针
  3. release 放弃对指针的控制权,返回指针,并将unque_ptr置为空

reset操作

#include <iostream>
#include <memory>

int main ()
{
    std::unique_ptr<int> up;  // 空

    up.reset (new int);       // 获取指针
    *up=5;
    std::cout << *up << '\n';//5

    up.reset (new int);       // 删除原对象,重新获取新的对象
    *up=10;
    std::cout << *up << '\n';//10

    up.reset();               // 删除对象

  return 0;
}

在unique_ptr作为参数传递和返回值时可以拷贝。

#include<bits/stdc++.h>
using namespace std;

unique_ptr<int> clone(int p=0)
{
    unique_ptr<int> ret(new int (p));
    return ret;
}
unique_ptr<int> pass(unique_ptr<int> p)
{
    *p=0;
    return p;
}

int main()
{
    cout<<*clone()<<endl;
    unique_ptr<int> up=clone(10);
    unique_ptr<int> new_p=pass(clone());
    cout<<*new_p<<endl;
    return 0;
}

release用法

#include <iostream>
#include <memory>

int main ()
{
    std::unique_ptr<int> auto_pointer (new int);
    int * manual_pointer;

    *auto_pointer=10;

    manual_pointer = auto_pointer.release();//auto_pointer把指向对象给manual_pointer
    // auto_pointer 为空

    std::cout << "manual_pointer points to " << *manual_pointer << '\n';

    delete manual_pointer;

    return 0;
}

weak_ptr类

指向一个shared_ptr管理的对象,将weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,而且weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。

weak_ptr主要操作

  1. w.reset 将w置空
  2. w.use_count 与w共享的shared_ptr的数量
  3. w.expired 如果use_count为0返回true,否则返回false
  4. w.lock 如果expired为1返回一个空的shared_ptr否则返回一个指向w的对象的shared_ptr

expire的例子,看了就理解了

#include <iostream>
#include <memory>

int main () 
{
    std::shared_ptr<int> shared (new int(10));
    std::weak_ptr<int> weak(shared);

    std::cout << "1. weak " << (weak.expired()?"is":"is not") << " expired\n";

    shared.reset();

    std::cout << "2. weak " << (weak.expired()?"is":"is not") << " expired\n";

    return 0;
}

lock的例子

#include <iostream>
#include <memory>

int main () {
    std::shared_ptr<int> sp1,sp2;
    std::weak_ptr<int> wp;
                                        // sharing group:
                                        // --------------
    sp1 = std::make_shared<int> (20);   // sp1
    wp = sp1;                           // sp1, wp

    sp2 = wp.lock();                    // sp1, wp, sp2
    sp1.reset();                        //      wp, sp2

    sp1 = wp.lock();                    // sp1, wp, sp2

    std::cout << "*sp1: " << *sp1 << '\n';
    std::cout << "*sp2: " << *sp2 << '\n';

  return 0;
}

上面的几个例子来自c plus plus,简单明了 直接拿来用了

有关动态内存分配

内存耗尽
使用new表达式申请内存时,如果内存耗尽,会抛出bad_alloc的异常
使用定位new的方式可以在内存分配失败时返回一个空指针,相当于向new表达式传递了一个参数

int *p2=new (nothrow) int(1);

释放内存

返回一个指向动态内存的指针时,要手动释放

如果不加上delete p会爆内存

int* fun()
{
    return new int[1000];
}
int main()
{
    for(int i=0;i<1000000;i++)
    {
        int *p=fun();
      //  delete [] p;
    }
    return 0;
}

使用指向动态内存的局部变量指针在内释放的情况。如果该指针在离开作用域之前未释放,会造成内存泄露

int* fun()
{
    return new int[1000];
}
void use()
{
    int *p=fun();
}

悬垂指针
使用被释放的指针

int main()
{
    int *p=new int[10];
    for(int i=0;i<10;i++)
        *(p+i)=i;
    delete [] p;
    for(int i=0;i<10;i++)
        cout<<*(p+i)<<endl;//使用被释放的指针
    return 0;
}

多个指针指向同一块内存
书上的例子

int *p(new int(42));
auto q=p;
delete p;
p=nullptr;

上面的q仍然指向被释放的内存

shared_ptr和unique_ptr指向数组
定义方式

unique_ptr<int[]> up(new int[10]);

当up销毁管理内存时,自动delete[]
当一个unique_ptr指向一个数组而不是单个对象时,不能使用点和箭头。可以使用下表运算符

unique_ptr<int[]> up(new int[10]);
for(size_t i = 0;i!=3;i++)
    up[i]=i;

shared_ptr不支持动态数组,如果想要使用shared_ptr管理动态数组,需要自己定义删除器

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

使用lambda表达式作为删除器

由于shared_ptr没有下表运算符,所以可以使用内置指针

#include<bits/stdc++.h>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);
    shared_ptr<int> sp(new int[10], [](int *p){ delete[] p;});
    for(size_t i=0;i!=10;i++)
        *(sp.get()+i)=i;
    for(size_t i=0;i!=10;i++)
        cout<<*(sp.get()+i)<<endl;
    sp.reset();
    return 0;
}

练习题12.23

#include<bits/stdc++.h>
using namespace std;

unique_ptr<char[]> connect_str(char *a,char *b)
{
    int la=strlen(a);
    int lb=strlen(b);
    unique_ptr<char[]> up(new char[la+lb]);
    for(size_t i=0;i!=la;i++)
        up[i]=a[i];
    for(size_t i=strlen(a);i!=la+lb;i++)
        up[i]=b[i-la];
    up[la+lb]='\0';
    return up;
}

int main()
{
    ios::sync_with_stdio(false);
    char a[]={"abc"};
    char b[]={"cde"};
    unique_ptr<char[]> uc=connect_str(a,b);
    for(size_t i=0;uc[i];i++)
        cout<<uc[i]<<endl;
    return 0;
}

to be continue~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值