我们经常听到内存泄漏,但是对这个抽象的概念一直没有什么理解,比如产生内存泄漏又将如何,我平时写程序从来不考虑这个等等。这篇的目的:第一,给大家实验实验内存泄露带来的问题,让大家直观感受内存泄露。第二,介绍C++11新特性智能指针shared_ptr、unique_ptr、weak_ptr。

目录

一、内存泄露

二、智能指针

2.1 shared_ptr

2.1.1 实现原理及代码

2.1.2 shared_ptr的使用

2.2 unique_ptr

2.2.1 unique_ptr原理及代码实现

2.2.2 unique_ptr使用方式

2.3 weak_ptr

2.3.1 原理及简单实现

2.3.2 weak_ptr的作用何在?

2.3.3 具体使用方法

三、总结


一、内存泄露

申请的内存未得到释放而引发的内存泄露指的是,程序在使用指针动态分配内存时,由于程序设计问题,没有释放已经不再使用的内存,造成程序的内存使用量逐渐增加。这会带来以下结果:

1. 如果内存泄露这个过程累积会占用大量内存,直至内存不足程序崩溃、系统假死。

2. 系统的性能会受到影响,系统会自动回收这些内存从而导致系统不流畅了。

3. 占用内存是实际的物理内存,会影响其他程序的运行,因为虚拟内存映射到物理内存是需要实际物理内存的,如果物理内存被占太多,就会导致其他程序无内存可用。

一个具体的例子:

下面是一段 C 语言代码,它用于动态分配内存来存储一个字符串:

char* myString = (char*) malloc(100);
strcpy(myString, "Hello, World!");
printf("%s\n", myString);

如果我们不释放 myString 指向的内存,那么这段代码就会导致内存泄露。为了避免内存泄露,我们应该在不再使用 myString 指向的内存后,调用 free 函数释放这段内存。

char* myString = (char*) malloc(100);
strcpy(myString, "Hello, World!");
printf("%s\n", myString);
free(myString);

这样就能避免内存泄露了。

问题:内存泄露在程序执行完后的内存会得到释放吗?

        回答:内存泄露在程序执行完后的内存并不会得到释放。当程序结束时,系统会释放程序所占用的所有资源,包括内存,但是这些内存中包含的未释放的空间并不会得到释放,而是被系统回收。

        如果程序存在大量的内存泄露,那么系统在程序结束时就需要释放大量的内存,这会导致系统性能下降。而且这些内存可能被其它程序或系统进程重新利用,这样会占用一些系统的资源,导致系统性能下降。

        为了避免这种情况的发生,应该尽早地发现和修复程序中的内存泄露问题,让程序能够及时释放不再使用的内存。

        问题:内存泄露的问题很严重吗,可以避免吗?有什么方法?

回答:内存泄露问题是一种常见的程序设计问题,在大型程序中特别容易发生。如果程序存在大量的内存泄露,那么它可能导致系统性能下降,甚至导致程序崩溃或系统假死。

        

尽管内存泄露问题是一种常见问题,但是可以通过一些方法来避免它:

  • 明确程序的内存使用计划,确保程序及时释放不再使用的内存。

  • 使用自动内存管理工具,如 C++ 的智能指针,来管理内存。

  • 使用内存检测工具,如 valgrind,在程序运行时自动检测内存泄露。

  • 在代码审查和测试过程中特别关注内存管理问题。

这四条建议貌似只有第二条是有实际作用的,其他的就像喊口号一样食之无味,弃之可惜。

二、智能指针

智能指针实现的原理很简单,通过使用对象去管理资源,并使用引用计数规则来进行统计对象使用次数,当使用次数减小至0时自动调用析构释放之前分配的内存,从而避免内存泄露。

我们来看一个简单的示例:

#include <iostream>
#include <memory>struct BigObj {BigObj() {std::cout << "big object has been constructed" << std::endl;}~BigObj() {std::cout << "big object has been destructed" << std::endl;}
};void test_shared_ptr() {BigObj *p = new BigObj();std::shared_ptr<BigObj> sp(p);std::shared_ptr<BigObj> sp1(new BigObj());std::shared_ptr<BigObj> sp2 = std::make_shared<BigObj>();
}std::shared_ptr<BigObj> get_obj() {return std::make_shared<BigObj>();
}int main() {test_shared_ptr();auto p = get_obj();}

输出:

big object has been constructed
big object has been constructed
big object has been constructed
big object has been destructed
big object has been destructed
big object has been destructed
big object has been constructed
big object has been destructed

可见,我们动态创建的4个BigObj指针对象都被释放了。

智能指针一共分为三种:shared_ptr、unique_ptr、weak_ptr。

2.1 shared_ptr

2.1.1 实现原理及代码

C++引入RAII特性,RAII可以保证任何情况下,使用对象先构造后析构。shared_ptr使用了计数器来统计对象被引用的次数,可表示shared_ptr思想的代码如下:

template<typename T>
class shared_ptr {
public:shared_ptr(T* p = nullptr) : ptr(p), count(new int(1)) {}~shared_ptr() {if (--*count == 0) {delete ptr;delete count;}}shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {++*count;}shared_ptr& operator=(const shared_ptr& other) {if (this != &other) {if (--*count == 0) {delete ptr;delete count;}ptr = other.ptr;count = other.count;++*count;}return *this;}T& operator*() { return *ptr; }T* operator->() { return ptr; }private:T* ptr;int* count;
};

每个 shared_ptr 对象都维护了一个指向该指针引用计数的指针,当拷贝一个 shared_ptr 对象时,引用计数就会加一;每当销毁一个 shared_ptr 对象时,引用计数就会减一。当引用计数为0时,就会自动释放动态分配的内存,避免内存泄露。

 shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {++*count;}

上面的代码 shared_ptr的拷贝构造,当一个 shared_ptr 对象被拷贝时,它会将指针和引用计数指针指向原来的 shared_ptr 对象。这样,原来的 shared_ptr 对象和新拷贝的 shared_ptr 对象都指向了同一个对象,并且共享了同一个引用计数。这样,就可以保证在所有 shared_ptr共享一份计数。

    ~shared_ptr() {if (--*count == 0) {delete ptr;delete count;}}

当 shared_ptr 对象被销毁时,它会先减少引用计数,如果引用计数变为0,则表示没有其它 shared_ptr 对象指向该对象了,那么就可以释放动态分配的内存,并释放引用计数所占用的内存。

    shared_ptr& operator=(const shared_ptr& other) {if (this != &other) {if (--*count == 0) {delete ptr;delete count;}ptr = other.ptr;count = other.count;++*count;}return *this;}

这段代码实现了 shared_ptr 的赋值操作符重载。它首先判断左操作数和右操作数是否相同,如果不同则将左操作数的引用计数减一,如果引用计数为0,就释放内存。然后将左操作数的指针和引用计数指针指向右操作数,并将右操作数的引用计数加1。这样,左操作数和右操作数就共享了同一个指针和引用计数,这就是 shared_ptr 的赋值操作的基本思路。

2.1.2 shared_ptr的使用

声明shared_ptr指针的方式:

std::shared_ptr<MyClass> ptr1(new MyClass);
//or
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>("Hello", 3.14);

具体例子:

#include <iostream>
#include <memory>class MyClass {public:MyClass() { std::cout << "MyClass constructed." << std::endl; }~MyClass() { std::cout << "MyClass destructed." << std::endl; }void DoSomething() { std::cout << "MyClass is doing something." << std::endl; }
};int main() {std::shared_ptr<MyClass> ptr1(new MyClass);ptr1->DoSomething();std::shared_ptr<MyClass> ptr2 = ptr1;ptr2->DoSomething();ptr1.reset();ptr2.reset();return 0;
}

定义了一个 MyClass 类,并在 main 函数中使用 shared_ptr 来管理该类的对象。首先,在 main 函数中,使用 std::shared_ptr<MyClass> ptr1(new MyClass); 创建了一个 shared_ptr 对象 ptr1,并使用 new 关键字动态分配了一个 MyClass 对象。然后,使用 ptr1->DoSomething(); 调用了 MyClass 的一个成员函数。

接着,使用 std::shared_ptr<MyClass> ptr2 = ptr1; 将 ptr1 的指针和引用计数拷贝给 ptr2,此时 ptr1 和 ptr2 共享了同一个 MyClass 对象。

最后,在程序结束前,使用 ptr1.reset(); 和 ptr2.reset(); 来释放动态分配的 MyClass 对象,并销毁 ptr1 和 ptr2。

运行结果:

MyClass constructed.
MyClass is doing something.
MyClass is doing something.
MyClass destructed.

2.2 unique_ptr

2.2.1 unique_ptr原理及代码实现

unique_ptr思想的实现:

template<typename T>
class unique_ptr {public:explicit unique_ptr(T* ptr = nullptr) : ptr_(ptr) {}~unique_ptr() { delete ptr_; }T* release() {T* tmp = ptr_;ptr_ = nullptr;return tmp;}T& operator*() const { return *ptr_; }T* operator->() const { return ptr_; }T* get() const { return ptr_; }private:T* ptr_;
};

unique_ptr 是一种独占所有权的智能指针,它只能有一个指针指向一个特定的对象。

unique_ptr 的构造函数接受一个指向 T 类型对象的指针,并将其赋值给类的成员变量 ptr_。         ~unique_ptr() 析构函数会自动调用 delete 释放动态分配的对象。

release() 函数可以释放 unique_ptr 对象所持有的指针,并将该指针返回。

operator* 和 operator-> 函数可以像普通指针一样使用 unique_ptr 对象,get() 函数可以获取 unique_ptr 对象所持有的指针。

2.2.2 unique_ptr使用方式

1. 创建unique_ptr并为其分配内存

std::unique_ptr<int> ptr1(new int(10));

2.  访问 unique_ptr 对象所指向的内存:

int x = *ptr1;
std::cout << x << std::endl;  // Output: 10

3.  释放 unique_ptr 对象所持有的指针:

int* raw_ptr = ptr1.release();

4.  移动语义

std::unique_ptr<int> ptr2(new int(20));
std::unique_ptr<int> ptr3 = std::move(ptr2);

2.3 weak_ptr

2.3.1 原理及简单实现

template<typename T>
class weak_ptr {public:weak_ptr() : ptr_(nullptr), count_(nullptr) {}weak_ptr(const shared_ptr<T>& other) : ptr_(other.ptr_), count_(other.count_) {}T* get() const {if (count_ && *count_ > 0) {return ptr_;}return nullptr;}shared_ptr<T> lock() const {if (count_ && *count_ > 0) {return shared_ptr<T>(ptr_, count_);}return shared_ptr<T>();}private:T* ptr_;int* count_;
};

weak_ptr 类包含了一个指向 T 类型对象的指针 ptr_ 和一个指向引用计数的指针 count_。

get() 函数返回所指向的对象的指针,但是会检查引用计数是否大于0.

lock()函数可以返回一个 shared_ptr 对象, 如果引用计数大于0,返回一个新的 shared_ptr 对象,否则返回一个空的 shared_ptr 对象。

weak_ptr(const shared_ptr<T>& other) : ptr_(other.ptr_), count_(other.count_) {}

这段代码是 weak_ptr 类的构造函数,它接受一个 shared_ptr<T> 类型的参数 other。通过 other 对象获取其管理的对象的指针 ptr_ 和引用计数指针 count_。然后将它们分别赋值给 weak_ptr 类的成员变量 ptr_ 和 count_。

2.3.2 weak_ptr的作用何在?

为解决循环引用问题。

循环引用是指两个或多个对象之间相互引用,而这些对象都不能被释放,因为它们都被另一个对象所引用。这样的话,这些对象就会成为内存泄露,导致程序无法正常工作。

而 weak_ptr 就可以在这种情况下解决这个问题。它可以访问一个 shared_ptr 所管理的对象,但不会增加该对象的引用计数。这样就可以解除循环引用,使得这些对象能够正常被释放。

举个例子,假设有一个类A,它有一个指针成员变量指向另一个类B, 类B也有一个指针成员变量指向类A,这样就出现了循环引用。 而使用 weak_ptr 就可以解除这种循环引用,使得两个类都能够正常被释放。

class A;
class B {std::weak_ptr<A> a_ptr;
};class A {std::shared_ptr<B> b_ptr;
};

在这个例子中,类A和类B互相持有对方的指针,但是使用了 weak_ptr 就可以解除循环引用。当类A的对象被销毁时,类B的 weak_ptr 对象不会增加A的引用计数,这样就不会出现循环引用。反之亦然。

2.3.3 具体使用方法

#include <iostream>
#include <memory>class A;class B {
public:std::weak_ptr<A> a_ptr;
};class A {
public:std::shared_ptr<B> b_ptr;~A() {std::cout << "A is deleted" << std::endl;}
};int main() {std::shared_ptr<A> a_ptr(new A);std::shared_ptr<B> b_ptr(new B);a_ptr->b_ptr = b_ptr;b_ptr->a_ptr = a_ptr;return 0;
}

类A和类B互相持有对方的指针,但是使用了 weak_ptr 就可以解除循环引用。当类A的对象被销毁时,类B的 weak_ptr 对象不会增加A的引用计数,这样就不会出现循环引用,从而可以避免内存泄漏。

运行结果为: A is deleted

在 weak_ptr 对象转换为 shared_ptr 对象之前,应该先使用 expired() 或 lock() 函数检查原先的 shared_ptr 对象是否已经销毁。

#include <iostream>
#include <memory>int main() {std::shared_ptr<int> shared_ptr_obj(new int);std::weak_ptr<int> weak_ptr_obj(shared_ptr_obj);if (weak_ptr_obj.expired()) {std::cout << "shared_ptr object has been deleted" << std::endl;} else {std::cout << "shared_ptr object is still alive" << std::endl;}shared_ptr_obj.reset();if (weak_ptr_obj.expired()) {std::cout << "shared_ptr object has been deleted" << std::endl;} else {std::cout << "shared_ptr object is still alive" << std::endl;}return 0;
}

结果:

shared_ptr object is still alive
shared_ptr object has been deleted

lock() 函数返回一个 shared_ptr 对象,如果引用的对象已经被销毁,则返回一个空的 shared_ptr 对象。

#include <iostream>
#include <memory>int main() {std::shared_ptr<int> shared_ptr_obj(new int);std::weak_ptr<int> weak_ptr_obj(shared_ptr_obj);std::shared_ptr<int> locked_ptr = weak_ptr_obj.lock();if (locked_ptr) {std::cout << "shared_ptr object is still alive" << std::endl;} else {std::cout << "shared_ptr object has been deleted" << std::endl;}shared_ptr_obj.reset();locked_ptr = weak_ptr_obj.lock();if (locked_ptr) {std::cout << "shared_ptr object is still alive" << std::endl;} else {std::cout << "shared_ptr object has been deleted" << std::endl;}return 0;
}

shared_ptr object is still alive
shared_ptr object has been deleted

这两个函数都可以用来检查原先的 shared_ptr 对象是否已经销毁,但是在使用时应根据具体情况来选择。如果只是需要知道原先的 shared_ptr 对象是否已经销毁,那么可以使用 expired() 函数。如果需要在原先的 shared_ptr 对象未销毁时访问其所管理的对象,那么可以使用 lock() 函数。

三、总结

智能指针的出现类似于加入了垃圾回收机制,一般来讲shared_ptr是日常使用最多的。使用智能指针可以提高程序的安全性和可读性,减少内存泄露和空指针错误。但是,智能指针会带来一些额外的开销,对性能有一定的影响。

首先,智能指针会增加对象的大小,因为需要存储引用计数和其他元数据。其次,使用智能指针会带来额外的内存分配和释放操作,当创建和销毁智能指针对象时会引起额外的内存开销。最后,使用智能指针会增加锁的使用,导致线程同步时产生额外的开销。总的来说,使用智能指针会带来一定的性能开销,但是能够带来的好处远大于开销。如果性能是关键的话,可以考虑使用自己实现的智能指针,或者在高性能代码中使用原始指针。

C++11 解决内存泄露问题的智能指针:shared_ptr、unique_ptr、weak_ptr相关推荐

  1. 32. 对c++中的smart pointer四个智能指针shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

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

  2. C++ 使用智能指针shared_ptr/unique_ptr管理数组

    目录 零.要管理的类 一.使用shared_ptr管理数组 二.使用unique_ptr管理数组 1.第一种方式 2.第二种方式 关于shared_ptr/unique_ptr的基础,我不在本篇博客中 ...

  3. C++11智能指针shared_ptr、weak_ptr、unique_ptr用法

    该博文为原创文章,未经博主同意不得转载,如同意转载请注明博文出处 本文章博客地址:https://cplusplus.blog.csdn.net/article/details/105065859 智 ...

  4. C++智能指针 shared_ptr、weak_ptr

    shared_ptr,共享指针应用于需要多个指针指向同一个对象的情况. 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论何时我们拷贝一个shared_ptr,计数器都 ...

  5. 乐鑫esp8266学习rtos3.0笔记第5篇:基于乐鑫idf框架,研究出超稳定、掉线重连、解决内存泄露问题的Mqtt框架,支持esp8266和esp32!(附带链接)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个" ...

  6. C/C++内存泄露,如何解决内存泄露?

    参考文章:vs2008如何查看内存空间在栈上还是堆上_C++ 如何避免内存泄露(文章从头到尾看了一遍,讲了很多方法,很多工具,比如智能指针SmartX之类,但是印象最深的还是在不使用工具的情况下,使用 ...

  7. 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 原创)智能指针拾遗 (原创)智能指 ...

  8. C++ 11 深度学习(六)智能指针综述

    以下三种智能指针均为类模板 1.shared_ptr  共享指针   ,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放. 2.week_ptr 是辅助shared_ptr工作的 3.u ...

  9. C++11新特性——智能指针之unique_ptr

    此课件及源代码来自B站up主:码农论坛,该文章仅作为本人学习笔记使用. unique_ptr独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁 ...

最新文章

  1. php判断邮箱是否合法性,php验证邮箱地址合法性
  2. tomcat的jsp页面超过65535,导致500报错
  3. python 安装nameerror_python NameError:name’file’未定义
  4. linux解压mysql文件命令行_linux mysql命令
  5. 【移动战略说·第十六期】创业者面临的机遇与挑战(杭州站)
  6. java反编译,eclipse支持插件
  7. 国服服务器_《Minecraft我的世界》第三方服务器的基本储备
  8. UVA 11552——Fewest Flops
  9. 【数据结构与算法】双链表V3.0的Java实现
  10. Base64 + 变为 空格 问题分析
  11. 【shell基础】10、函数
  12. (23)FPGA面试题常用逻辑电平
  13. 《数据结构》陈越课件重点总结
  14. sql 去除数据表中一列中字符串后边的空格...
  15. 我数学不好,适合做程序员吗?
  16. mybatis3的中文官方文档
  17. NDTTS-II变压器综合试验系统
  18. [整理]WebUploader + SpringMVC 实现多文件断点续传之一 多文件上传
  19. 课设——八皇后问题(N皇后解决)
  20. Linux_03_文档内容相关命令

热门文章

  1. 基础练习:1085:球弹跳高度的计算
  2. iOS 快速解决~倒计时button闪烁的问题
  3. 计算几何常用算法[转]
  4. 小白入智能小车坑(一)
  5. JavaScript前世今生
  6. PC微信逆向--在线备份数据库
  7. 软件测试学习 之 Python 函数默认参数
  8. 【IO】SBIO,SNBIO,ANIO,AIO
  9. 02-PS工具栏介绍
  10. Python导出当前环境依赖包及项目依赖包