我们在程序运行的过程中,经常出现段错误、内存持续增大等,是C++显式内存管理存在的问题,主要归纳为以下几点:

  1. 野指针:一些内存单元已经释放,但之前指向它的指针还在使用。
  2. 重复释放:程序试图释放已经被释放过的内存单元。
  3. 内存泄漏:没有释放不再使用的内存单元。
  4. 缓冲区溢出:数组越界。
  5. 不配对的new[]/delete

针对以上1~3的问题,C++标准中提供了智能指针来解决。
智能指针是基于RAII(Resource Acquisition Is Initialization)机制实现的类(模板),具有指针的行为(重载了operator*与operator->操作符)。当对象创建的时候,进行初始化;离开其作用域后,通过自动调用析构函数释放资源。

C++98中,智能指针通过一个模板类型"auto_ptr"来实现,auto_ptr对象通过初始化指向由new创建的动态内存,当auto_ptr对象生命周期结
束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。但其有一些缺点:

  • 赋值和拷贝操作的目标对象会先释放其原来所拥有的对象

    auto_ptr<int> ap1(new int(100));
        auto_ptr<int> ap2(ap1);
        // ap1 == nullptr;

    auto_ptr 不能用在stl容器中,因为stl容器要求存储的类型必须为值语义类型。即两个对象在拷贝或赋值之后相等(ap1 == ap2)

  • auto_ptr 析构的时候调用的是delete,所以不能用auto_ptr管理数组指针
    ~auto_ptr() { delete _M_ptr; }

因此,在C++11标准中,改用unique_ptr、shared_ptr及weak_ptr等智能指针来对动态内存进行管理。auto_ptr为了兼容以前的代码被遗留下来,不建议使用。

头文件

<memory>

命名空间为

std

unique_ptr

概念:
  • unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现std::move())。* unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现std::move())。
  • unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
  • unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
基本用法:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Foo {
    Foo() {}
    ~Foo() {}
    void Print() { cout << "Foo" << endl; }
};
int main(void)
{
    Foo* p1 = new Foo();
    unique_ptr<Foo> up1;       // up1==nullptr
//  up1 = p1;                  // 编译错误,不支持这样赋值
    up1.reset(p1);             // 替换管理对象,并释放之前管理的对象
    p1 = nullptr;
//  unique_ptr<Foo> up2(up1);  // 编译错误,不支持这样构造
    unique_ptr<Foo> up2(std::move(up1)); // up1所有权转移到up2。up1==nullptr
    up1.swap(up2);             // up2与up1管理对象的指针交换。 up2==nullptr
    if (up1) {                 // up1 != nullptr
        up1->Print();          // unique_ptr重载了->
        (*up1).Print();        // unique_ptr重载了*
    }
//  up2->Print();              // 错误 up2 == nullptr, 必须先判断再调用
    p1 = up1.get();            // get() 返回所管理对象的指针, up1继续持有其管理权
    p1 = up1.release();        // release() 返回管理对象的指针,并释放管理权,up1==nullptr
    delete p1;
    unique_ptr<Foo> up3(new Foo());
    up3.reset();                // 显示释放释放管理对象的内存,也可以这样做:up = nullptr;
    vector<unique_ptr<Foo>> v;
    unique_ptr<Foo> up4(new Foo());
//  v.push_back(up4);            // 编译错误,不支持这样拷贝
    v.push_back(std::move(up4);  // 只能up4放弃对其所有权,通过std::move()将所有权转移到容器中
    return 0;
}

应用场景:
  • 只要unique_ptr智能指针创建成功,其析构都会被调用,确保动态资源的释放------避免内存泄漏。
  • 把unique_ptr作为引用参数,传递给其他例程,不用担心该指针在例程中被copy一份,或不小心释放掉。

shared_ptr

概念:
  • shared_ptr 基于“引用计数”模型实现, 多个shared_ptr对象可以拥有同一个动态对象,并维护了一个共享的引用计数。当最后一个指向该对象的shared_ptr被销毁或者reset时,会自动释放其所指的对象,回收动态资源。
  • 销毁该对象时,使用默认的delete/delete[]表达式,或者是在构造 shared_ptr 时传入的自定义删除器(deleter),以实现个性化的资源释放动作。
基本用法:
#include <iostream>
#include <memory>
#include <vector>
#include <assert.h>
using namespace std;
struct Foo {
    int v;
};
int main(void)
{
    shared_ptr<Foo> sp1(new Foo{10});
    cout << sp1.unique() << endl;    // 1 当前shared_ptr唯一拥有Foo管理权时,返回true,否则返回false
    cout << sp1.use_count() << endl; // 1 返回当前对象的引用计数
    shared_ptr<Foo> sp2(sp1);
    assert(sp1->v == sp2->v);        // sp1与sp2共同拥有Foo对象
    cout << sp2.unique() << endl;    // 0 false
    cout << sp2.use_count() << endl; // 2
    sp1.reset();                     // 释放对Foo的管理权,同时引用计数减1
    assert(sp1 == nullptr);          // sp1 为空
    cout << sp1.unique() << endl;    // 0 不会抛出异常
    cout << sp1.use_count() << endl; // 0 不会抛出异常
    cout << sp1.get() << endl;       // 0 不会跑出异常
//  cout << sp1->v << endl;          // 执行错误 sp1为nullptr时,operator* 和 operator-> 都会导致未定义行为
    cout << sp2.unique() << endl;    // 1 true
    sp1.swap(sp2);                   // sp1与sp2交换管理权,及引用计数
    assert(sp2 == nullptr);
    cout << (*sp1).v << endl;        // 10 同sp1->v相同
    vector<shared_ptr<Foo>> vec;
    vec.push_back(sp1);
    cout << sp1.use_count() << endl; // 2
    return 0;
// vector先析构,里面存储的对象引用计数减1,但不为0,不释放对象
// sp1后析构,引用计数减1,变为0,释放所指对象的内存资源
}

主要方法:
T& operator*() const;
T* operator->() const;
T* get() const;
bool unique() const;
long use_count() const;
void swap(shared_ptr<T>& b);

应用场景:

1. 在一个map(unordered_map)中,多key索引一个value,使用shared_ptr。

#include <iostream>
#include <memory>
#include <map>
#include <cstdint>
using namespace std;
Class SessionNode {
public:
    SessionNode() {}
    virtual ~SessionNode() {}
};
typedef std::shared_ptr<SessionNode> SessionNodeSP;
int main(void)
{
    map<uint64_t, SessionNodeSP> map;
    uint64_t imsi = 4600000;
    uint32_t tmsi = 0x12345;
    {
        SessionNodeSP sp(new SessionNode());
        map[imsi] = sp;
        map[tmsi] = sp;
        cout << sp.use_count() << endl;  // 3
    } // sp 销毁,引用计数减1,变为2
    map.erase(tmsi);  // use_count()为1
    map.erase(imsi);  // use_count()为0,释放被管理对象的内存
    return 0;
}

2. 多个map索引同一value

#include <iostream>
#include <memory>
#include <map>
#include <cstdint>
using namespace std;
struct Node {
    uint64_t imsi;
    uint32_t tmsi;
};
typedef std::shared_ptr<Node> NodeSP;
class Session {
public:
    Session() {}
    ~Session() {}
    NodeSP GetNode(uint64_t imsi, uint32_t tmsi) {
        NodeSP sp(new Node{imsi, tmsi});
        imsi_map_[imsi] = sp;
        tmsi_map_[tmsi] = sp;
        return sp;
    }
    void EraseNode(NodeSP& sp) {
        if (sp == nullptr)
            return;
        imsi_map_.erase(sp->imsi);
        imsi_map_.erase(sp->tmsi);
    }
private:
    map<uint64_t, NodeSP> imsi_map_;
    map<uint32_t, NodeSP> tmsi_map_;
};
int main(void)
{
    Session ses;
    uint64_t imsi = 4600000;
    uint32_t tmsi = 0x12345;
    NodeSP sp;
    sp = ses.GetNode(imsi, tmsi);
    cout << sp.use_count() << endl; // 3
    // ... do something with sp
    ses.EraseNode(sp);
    cout << sp.use_count() << endl; // 1
    sp.reset(); // 主动释放被管理对象内存
    return 0;
}

3. 防止裸指针被删除

#include <iostream>
#include <memory>
class Cdr  {
public:
    Cdr() {}
protected:
    virtual ~Cdr() {}
};
class SignalCdr : public Cdr {
public:
    SignalCdr() {}
    virtual ~SignalCdr() {}
};
typedef std::shared_ptr<Cdr> CdrSP;
CdrSP CreateSignalCdr()
{
    CdrSP sp(new SignalCdr());
    return sp;
};
int main(void)
{
    CdrSP sp_cdr = CreateSignalCdr();
    SignalCdr* p_signal_cdr = reinterpret_cast<SignalCdr*>(sp_cdr.get());
    // ... do something
//  delete p_signal_cdr; // 执行错误,~Cdr()为protected
    return 0;
}

4. 定制删除器。

#include <iostream>
#include <memory>
#include <errno.h>
#include <stdio.h>
#include <string.h>
using namespace std;
class FileCloser {
public:
    void operator()(FILE* p) {
        // ... do something
        cout << "close file" << endl;
        fclose(p);
    }
};
int main(void)
{
    try {
        FILE* p_file = fopen("./test.cpp", "r");
        if (p_file == nullptr)
            throw errno;
        shared_ptr<FILE> sp_file(p_file, FileCloser());
//      shared_ptr<FILE> sp_file(p_file, &fclose);  //如果只需要调用一个单参数的函数,可以直接这么写
        // ... do something
    } catch (int& err) {
        cout << strerror(err) << endl;
    }
    return 0;
}

5. 在引起循环引用的时候使用weak_ptr。

#include <iostream>
#include <memory>
using namespace std;
struct Husband;
struct Wife;
typedef std::shared_ptr<Husband> HusbandSP;
typedef std::shared_ptr<Wife>    WifeSP;
struct Wife {
    ~Wife() { cout<< "wife distroy" << endl; }
    HusbandSP sp_hb;
};
struct Husband {
    ~Husband() { cout<< "husband distroy" << endl; }
    WifeSP sp_wf;
};
int main(void)
{
    {
        HusbandSP husband(new Husband());
        WifeSP    wife(new Wife());
        husband->sp_wf = wife;
        wife->sp_hb = husband;
    } // husband 和 wife,相互引用,离开作用域时引用计数都为1,造成内存泄露
    return 0;
}

weak_ptr

概念:
  • weak_ptr是为了配合shared_ptr而引入的一种智能指针,它只能够通过shared_ptr或者weak_ptr来构造。
  • weak_ptr是作为shared_ptr的”观察者“,并不修改shared_ptr所管理对象的引用计数,当shared_ptr销毁时,weak_ptr会被设置为空,所以使用weak_ptr比底层指针的好处在于能够知道所指对象是否有效
  • weak_ptr不具有普通指针的行为,因为没有重载operator*和->。所以当weak_ptr观察的对象存在,并且需要修改其内容时,需要提升为shared_ptr来操作。
基本用法:
#include <iostream>
#include <memory>
using namespace std;
struct Cdr{
    int v;
};
typedef std::shared_ptr<Cdr> CdrSP;
typedef std::weak_ptr<Cdr>   CdrWP;
void UpdateCdr(CdrWP wp)
{
    if (wp.expired() == false) {   // 检查被管理对象是否被删除,true 删除,false 没被删除;比use_count()==1要快
        CdrSP sp = wp.lock();      // 提升为强引用
//      CdrSP sp(wp);              // 另一种wp提升为强引用方法
        if (sp != nullptr) {       // 若提升失败,shared_ptr 为 nullptr,此例子不会失败
            sp->v *= 2;
            cout << sp.use_count() << endl; // 此时引用计数为2
        }
    } // sp删除,引用计数减1
    wp.reset(); // 显示释放所有权,或者等离开作用域会自动释放
}
int main(void)
{
    CdrSP sp(new Cdr{10});
    UpdateCdr(sp);  // 对sp进行操作
    cout << sp->v << endl;  // 20
    return 0;
}

主要方法:
long use_count() const;
bool expired() const;
std::shared_ptr<T> lock() const;

应用场景:
  1. 如基本用法,通过weak_ptr观察shared_ptr管理的对象,如果有效,提升为shared_ptr进行操作。
  2. map中存储弱引用
    #include <iostream>
    #include <memory>
    #include <map>
    using namespace std;
    struct Cdr {
        int v;
    };
    typedef std::shared_ptr<Cdr> CdrSP;
    typedef std::weak_ptr<Cdr>   CdrWP;
    class Cache {
    public:
        Cache() {}
        ~Cache() {}
        void UpdateIndex(CdrSP& sp) {
            if (sp != nullptr && sp->v > 0)
                cdr_map_[sp->v] = sp;
        }
    private:
        map<int, CdrWP> cdr_map_;
    };
    int main(void)
    {
        Cache cache;
        CdrSP sp_cdr(new Cdr{1});
        cache.UpdateIndex(sp_cdr);
        cout << sp_cdr.use_count() << endl; // 1
        return 0;
    // sp_cdr销毁,引用计数为1,释放管理对象内存
    // cache销毁,因为map中存储的为weak_ptr,weap_ptr为空,所以不会产生二次释放
    }

bad_weak_ptr异常捕获

当shared_ptr通过weak_ptr参数构造,而weak_ptr指向一个已经被删除的对象时,会抛出std::bad_weak_ptr异常。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    shared_ptr<int> sp1(new int(1));
    weak_ptr<int> wp(sp1);
    p1.reset();
    try {
        shared_ptr<int> sp2(wp);
    } catch (const std::bad_weak_ptr& e) {
        cout << e.what() << endl;    // "std::bad_weak_ptr"
    }
}

从this创建shared_ptr

有时候,需要从this获得 shared_ptr ,即是说,你希望你的类被shared_ptr所管理,你需要把"自身"转换为shared_ptr的方法。

#include <iostream>
#include <memory>
using namespace std;
class Foo;
typedef std::shared_ptr<Foo> FooSP;
void DoSomething(const FooSP& sp) {
    cout << sp.use_count() << endl; // 2
}
class Foo : public std::enable_shared_from_this<Foo> {
public:
    Foo() {}
    ~Foo() {}
    void Do() {
        DoSomething(shared_from_this());
    }
};
int main(void)
{
    FooSP sp(new Foo());
    cout << sp.use_count() << endl; // 1
    sp->Do();
    return 0;
}

总结:

  • unique_ptr/shared_ptr中,get()把底层指针暴露出来,为的是兼容老程序,一般不提倡使用,因为很难确保别的例程会对这个指针做什么,比如说delete/delete[]。
  • 作为shared_ptr和weak_pt作为参数传递时,使用引用传递以减小开销。
  • weak_ptr 没有重载operator==,所以不能比较。shared_ptr可以比较,比较的是里面底层指针。
  • auto_ptr、unique_ptr、shared_ptr,不能这样用
    {
        int* p = new int(100);
        auto_ptr<int> ap1(p);
        auto_ptr<int> ap2(p);
    }

    当离开作用域,ap1和ap2都试图删除p,会造成double free。

  • 使用智能指针虽然不需要手动处理引用计数和调用delete来释放资源。但要清楚什么时候资源会被释放。
    例如:用容器来存储shared_ptr,并且从容器中删除的时候释放资源,那么其他例程在使用shared_ptr时只是更新其资源;
    如果从容器中删除shared_ptr时不释放资源,那么应该被另外一个shared_ptr所共享;这个时候容器中存储weak_ptr更合适。

引用:

英文网址:
http://en.cppreference.com/w/cpp/memory

中文网址(google 机器翻译):
http://zh.cppreference.com/w/cpp/memory

标签:

转载于:https://www.cnblogs.com/457220157-FTD/p/4129058.html

c++11:智能指针相关推荐

  1. 详解C++11智能指针

    详解C++11智能指针 前言 C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用 ...

  2. 【C++11新特性】 C++11智能指针之weak_ptr

    http://blog.csdn.net/xiejingfa/article/details/50772571 原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/ar ...

  3. c++11新特性_【C++11新特性】 C++11智能指针之weak_ptr

    如题,我们今天要讲的是 C++11 引入的三种智能指针中的:weak_ptr. 在学习 weak_ptr 之前最好对 shared_ptr 有所了解.如果你还不知道 shared_ptr 是何物,可以 ...

  4. C++11智能指针(unique_ptr、shared_ptr、weak_ptr)boost::scoped_ptr

    C++11智能指针(unique_ptr.shared_ptr.weak_ptr)_-码农小非-的专栏-CSDN博客_c++ shared_ptr weak_ptr 原创)智能指针拾遗 (原创)智能指 ...

  5. C++11 智能指针时要避开的 10 大错误

    我很喜欢C++11全新的智能指针,对于那些苦恼于自己管理内存的开发者来说,这是天大的好事.但是,在广泛使用C++11智能指针的两年里,因为使用方法不当而导致程序的效率下降甚至直接crash掉的事情发生 ...

  6. 使用 C++11 智能指针时要避开的 10 大错误

    http://blog.jobbole.com/104666/ 我很喜欢新的C++11的智能指针.在很多时候,对很多讨厌自己管理内存的人来说是天赐的礼物.在我看来,C++11的智能指针能使得C++新手 ...

  7. 十大愚蠢的错误,以避免与C ++ 11智能指针

    Top 10 dumb mistakes to avoid with C++ 11 smart pointers I love the new C++ 11 smart pointers. In ma ...

  8. c++11 智能指针 unique_ptr、shared_ptr与weak_ptr

    C++11中有unique_ptr.shared_ptr与weak_ptr等智能指针(smart pointer),定义在<memory>中. 可以对动态资源进行管理,保证任何情况下,已构 ...

  9. 窥见C++11智能指针

    导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针,让我们从指针的内存管理中释放出来,几乎消灭所有new和delete.既然智能指针如此 ...

  10. 【C++11新特性】 C++11智能指针之shared_ptr

    http://blog.csdn.net/Xiejingfa/article/details/50750037 原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/ar ...

最新文章

  1. CentOS7环境搭建使用PhpStorm上传代码
  2. 谷歌AI研究院:被低估的数据,被高估的模型
  3. Nature论文爆出千行Python代码Bug,或影响百篇学术论文
  4. android自助终端界面_ZTHP500 | 桌面式人脸消费终端
  5. VC,VB程序button、图标样式美化
  6. 【ArcGIS遇上Python】ArcGIS Python实现长时间序列遥感影像批量处理--以裁剪为例
  7. Windows安装Python包下载工具pip遇到的问题
  8. 作者:陈维政,男,北京大学博士生。
  9. 【详解】消息队列和线程关系
  10. LightGBM图解理论+视频+安装方法+python代码
  11. 百度深度学习初级工程师认证划水贴
  12. IDEA炫酷主题推荐!非常好看!
  13. c#控制台应用程序读取 config
  14. NETCTOSS代码实现第二版
  15. js实现oss批量下载文件_远程URL文件批量下载打包的方法
  16. 2019 年第 32 周 DApp 影响力排行榜 | TokenInsight
  17. 从函数型数据到光滑函数
  18. 行测测评(二)——图形找规律技巧
  19. 数据库候选关键词怎么求_数据库中候选码求法.(比较全的哦)
  20. 计算机应用技术高数学几本书,高一下学期数学学必修几 学哪几本书

热门文章

  1. 汇编语言——第2次上机实验
  2. C语言中有bool变量吗?
  3. 堆(概念,数据结构中堆与内存堆区的区别 ,堆的基本操作)
  4. 类模板static成员的使用
  5. C语言extern的用法
  6. Ubuntu停止维护版本的软件源配置和系统升级方法
  7. 【操作系统】进程调度(4):I/O、不可预测的运行时间
  8. 95. 不同的二叉搜索树 II
  9. 【转】Java Socket编程基础及深入讲解
  10. 15 并发编程-(IO模型)