c++ 动态内存与智能指针详解

一、 动态内存

(一)程序对象的生存期

  • 全局对象在程序启动时分配,在程序结束时销毁。

  • 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。

  • 局部static对象在第一次使用前分配,在程序结束时销毁。

  • C++还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

(二) 动态内存管理

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

(三) RAII

  1. 什么是RAII?

RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

  1. 如何实现RAII?

利用编译器自动创建、销毁局部对象的特点,可以实现RAII:

a.设计一个类封装资源

b.在构造函数中初始化(创建对象、加锁)

c.在析构函数中执行销毁操作 (销毁对象、解锁)

d.使用时声明一个该对象的类

  1. 用途:通过类封装资源,实现动态内存的创建和回收,实现互斥锁的加锁与开锁。

二、RAII的应用——智能指针

为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,**重要的区别是它负责自动释放所指向的对象**。

(一)智能指针shared_ptr和unique_ptr共有操作:

  1. p->mem

    即(*p).mem

  2. p.get()

    返回保存的指针

  3. swap(p, q)p.swap(q)

    交换p、q中的指针

(二)shared_ptr类

shared_ptr<T>允许多个指针指向同一个对象; 在内部维护一个引用计数。shared_ptr类只有两个指针成员,一个指针是所管理的数据的地址;还有一个指针是控制块的地址,包括引用计数、weak_ptr计数、删除器、分配器。其中引用计数的存储是在堆上的。 因此一个shared_ptr对象的大小是raw_pointer大小的两倍。


element_type* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
  1. make_shared<T> p(q)

    p是q的拷贝,此操作递增q中的计数器。q中的指针必须能转换为T*。

  2. make_shared<T>()

    允许传入的参数与T的某个构造函数匹配。

  3. p.unique()

  4. p.use_count()

  5. p.reset()p.reset(q)

  • p.reset(); //释放p中内置的指针指向的空间
  • p.reset(q); //将p中内置指针换为q,并且用d来释放p之前所指的空间
  1. 拷贝和赋值

    • 拷贝:无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。

    • 赋值:当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域时,计数器就会递减。

    auto r = make_shared<int>(42);
    r = q; //给r赋值,另其指向另一个地址
    //递增q所指向对象的引用计数
    //递减r原来所指对象的引用技术
    //r原来指向的对象已没有引用者,会自动释放内存。
  2. 自动销毁所管理的对象:

    当指向一个对象的最后一个shared_ptr被销毁时,即引用计数为0时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数——析构函数(destructor)完成销毁工作的。

  3. 转移控制权

    sp2 = std::move(sp1)

(三)unique_ptr类

unique_ptr则“独占”所指向的对象。

template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
(1) (since C++11) template <
class T,
class Deleter
> class unique_ptr<T[], Deleter>;
(2) (since C++11)
  1. 不能拷贝和赋值,对应拷贝构造函数和赋值运算符函数已定义删除。

        // Disable copy from lvalue.
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
  2. 可以用raw pointer进行reset。

    sp.reset(*TYPE)
  3. 释放所有权 sp.release()

  4. 转移控制权 std::move(sp)


    #include <iostream>
    #include <memory>
    using namespace std; int main(){
    unique_ptr<int> p1 = make_unique<int>(1);
    unique_ptr<int> p2 = make_unique<int>(2);
    p1.swap(p2); //交换指针
    cout<<*p1<<endl;
    cout<<*p2<<endl;
    int* a = new int(3);
    p1.reset(a); //用*Type类型重置p1
    cout<<*p1<<endl;
    p1.release(); //释放所有权
    unique_ptr<int> p3 = std::move(p2); // 转移控制权
    cout<<*p3<<endl; return 0;
    } // 2
    // 1
    // 3
    // 1

(四)weak_ptr类

https://blog.csdn.net/Xiejingfa/article/details/50772571

  1. 标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。不论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。用于解决shard_ptr中可能存在的循环引用而导致内存泄露的问题。

  2. weak_ptr对它所指向的shared_ptr所管理的对象没有所有权,若要读取引用对象,必须要转换成shared_ptr。

  3. 如何判断weak_ptr指向对象是否存在呢?

    C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr。

    shared_ptr<A> pa = wp.lock()

    #include <memory>
    #include <iostream>
    using namespace std;
    void Check(weak_ptr<int> & wp) {
    shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr)
    cout << "still " << *sp << endl;
    else
    cout << "pointer is invalid." << endl;
    }
    int main() {
    shared_ptr<int> sp1(new int(22));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
    cout << *sp1 << endl; // 22
    cout << *sp2 << endl; // 22
    Check(wp); // still 22
    sp1.reset();
    cout << *sp2 << endl; // 22
    Check(wp); // still 22
    sp2.reset();
    Check(wp); // pointer is invalid
    }
  4. 一些内置方法

    https://en.cppreference.com/w/cpp/memory/weak_ptr

    Modifiers Description
    reset() releases the ownership of the managed object
    swap() swaps the managed objects
    Obeservers Description
    use_count() returns the number of shared_ptr objects that manage the object
    expired() checks whether the referenced object was already deleted
    lock() creates a shared_ptr that manages the referenced object
    owner_before() provides owner-based ordering of weak pointers

三、关于智能指针的更多问题

(1)自定义删除器 Deleter

实例见:

https://blog.csdn.net/caroline_wendy/article/details/16938707

"shared_ptr"的传递删除器(deleter)方式比较简单, 只需要在参数中添加具体的删除器函数名, 即可; 注意是单参数函数;

"unique_ptr"的删除器是函数模板(function template), 所以需要在模板类型传递删除器的类型(即函数指针(function pointer)), 再在参数中添加具体删除器;

(2)shared_ptr的线程安全问题

https://blog.csdn.net/www_dong/article/details/114418454

  • shared_ptr引用计数的增减通过原子操作来实现,因此是线程安全的。
  • shared_ptr指针所指向的资源被多个线程读写时,会存在线程安全的问题,不是线程安全的。

(3)智能指针SmartPointer与裸指针RawPointer效率对比问题

memory-and-performance-overhead-of-smart-pointer

how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c

  1. shared_ptr由于占据更多内存,且需要通过原子操作维护引用计数,因此效率是比较慢的。在不开启编译器优化的时候,是比new操作慢10倍,此时不应该使用make_shared、shared_ptr。开启优化后,也大概慢2-3倍。
  2. unique_ptr、make_unique、带少许偏差的make_shared几乎和new、delete具有一样的性能。
  3. unique_ptr自动管理内存资源,而几乎没有额外开销。因此效率和new\delete几乎一样。

(4)智能指针的应用场景

1. shared_ptr

  • shared_ptr 内部有引用计数,在对象所有权需要共享的时候(share)用,shared_ptr 具有赋值拷贝的语义。

  • 作为需要保存在容器里的对象,同时避免频繁创建引起性能上的开销。如果一个类的创建需要比较多的资源(例如比较大的的内存和拷贝),如果我们直接保存在容器里可能会在拷贝时产生比较大的性能损失,这个时候可以考虑使用shared_ptr进行资源共享,然后将shared_ptr保存于容器。

     vector<std::shared_ptr<Foo>> foos;
// ...
for(auto &foo : foos){
process_func(*foo);
}

C++ 三种智能指针的使用场景

原文链接: https://xie.infoq.cn/article/d7850479aa075a82126099de6

2. unique_ptr

独占对象的拥有权。C++14通过make_unique创建unique_ptr,是一种更加异常安全的做法。

  /// std::make_unique for single objects
template<typename _Tp, typename... _Args>
inline typename _MakeUniq<_Tp>::__single_object
make_unique(_Args&&... __args)
{ return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); } /// std::make_unique for arrays of unknown bound
template<typename _Tp>
inline typename _MakeUniq<_Tp>::__array
make_unique(size_t __num)
{ return unique_ptr<_Tp>(new remove_extent_t<_Tp>[__num]()); } /// Disable std::make_unique for arrays of known bound
template<typename _Tp, typename... _Args>
inline typename _MakeUniq<_Tp>::__invalid_type
make_unique(_Args&&...) = delete;

3. weak_ptr

  • 打破shared_ptr循环引用导致的内存泄露,例如使用weak_ptr 保存二叉树的 parent 节点。
#include <memory>

struct node
{
std::shared_ptr<node> left_child;
std::shared_ptr<node> right_child;
std::weak_ptr<node> parent;
foo data;
};
  • 缓存

  • 观察者模式的订阅者

    • 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

    观察者模式详解

    • 缺点:

      它的主要缺点是目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

(5)enable_shared_from_this模板类

  • 作用:用于在类对象的内部中获得一个指向当前对象的 shared_ptr 对象

C++11中enable_shared_from_this的用法解析

cppreference enable_shared_from_this

  • 直接使用this不行吗?

假如this被push_back()到一个shared_ptr的vector,会通过this为该对象的内存块创建一个新的智能指针而生成新的控制块,则会可能出现未知错误。

Effective Modern C++:

std::enable_shared_from_this defines a member function that creates a std::shared_ptr to the current object, but it does it without duplicating control blocks. The member function is shared_from_this, and you use it in member functions whenever you want a std::shared_ptr that points to the same object as the this pointer.

  • 使用方法:

继承enable_shared_from_this类;通过shared_from_this()方法返回

(6)智能指针模板中的类型可以是数组吗?

https://stackoverflow.com/questions/13061979/can-you-make-a-stdshared-ptr-manage-an-array-allocated-with-new-t

c++11 动态内存与智能指针详解的更多相关文章

  1. 【足迹C++primer】39、动态内存与智能指针(3)

    动态内存与智能指针(3) /** * 功能:动态内存与智能指针 * 时间:2014年7月8日15:33:58 * 作者:cutter_point */ #include<iostream> ...

  2. 12.动态内存和智能指针、 直接管理内存、shared_ptr和new结合使用

    12.动态内存和智能指针 1.智能指针分为两种shared_ptr和unique_ptr,后者独占所指向的对象.智能指针也是模板,使用时要用尖括号指明指向的类型.类似emplace成员,make_sh ...

  3. [转]C++ 智能指针详解

    转自:http://blog.csdn.net/xt_xiaotian/article/details/5714477 C++ 智能指针详解 一.简介 由于 C++ 语言没有自动内存回收机制,程序员每 ...

  4. C++ 智能指针详解(转)

    C++ 智能指针详解   一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常 ...

  5. 【C++】智能指针详解(一):智能指针的引入

    智能指针是C++中一种利用RAII机制(后面解释),通过对象来管理指针的一种方式. 在C++中,动态开辟的内存需要我们自己去维护,在出函数作用域或程序异常退出之前,我们必须手动释放掉它,否则的话就会引 ...

  6. [C++11新特性] 智能指针详解

    动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极为困难的.有时我们会忘记释放内存产生内存泄漏,有时提前释放了内存,再使用指针去引用内存就会报错. 为了更容易(同时也更安全)地使用动态内存, ...

  7. 【C++】动态内存与智能指针

    C++常见的内存分配方式有三种: 从静态存储区分配,这里主要是存储局部static对象,类的static成员以及定义在函数之外的变量: 从栈内存分配,这里主要是存储函数内的非static对象: 从堆内 ...

  8. C++相关:动态内存和智能指针

    前言 在C++中,动态内存的管理是通过运算符new和delete来完成的.但使用动态内存很容易出现问题,因为确保在正确的时间释放内存是及其困难的.有时候我们会忘记内存的的释放,这种情况下就会产生内存泄 ...

  9. c++学习笔记—动态内存与智能指针浅析

    我们的程序使用内存包含以下几种: 静态内存用来保存局部static对象.类static数据成员以及定义在任何函数之外的变量,在使用之前分配,在程序结束时销毁. 栈内存用来保存定义在函数内部的非stat ...

  10. C++11 unique_ptr智能指针详解

    在<C++11 shared_ptr智能指针>的基础上,本节继续讲解 C++11 标准提供的另一种智能指针,即 unique_ptr 智能指针. 作为智能指针的一种,unique_ptr ...

随机推荐

  1. 虚拟 DOM 实现原理?

    虚拟 DOM 的实现原理主要包括以下 3 部分: 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象: diff 算法 - 比较两棵虚拟 DOM 树的差异: pach 算法 ...

  2. flutter 一直卡在Running Gradle task 'assembleDebug'...运行不起来

    大概率只有一个原因:gradle下载不完整! 要想办法让他下载完整! 解决方法: 方法一:修改远程maven仓库地址(2024.7.9下列地址可用) repositories{ maven{ url' ...

  3. Memcache 与 Memcached 的区别

    Memcached 从0.2.0开始,要求PHP版本>=5.2.0,Memcache 要求PHP版本>=4.3. Memcached 最后发布时间为2018-12-24,Memcache ...

  4. 只会建数据库怎么写API?database2api 能帮到你!

    database2api 意为 DataBase to API,即只要有数据库,就可以生成开放 API. database2api 是一款强大而便捷的工具,主要功能是依据现有的数据库自动生成开放的 A ...

  5. 支付宝小程序的级联选择器,对接简单操作,Cascader 级联选择器element_ui

    首先,对于element_ui 的动接,由于需要数据格式是 但是支付宝提的接口返回的数据是另一种格式,并且支付宝的三级联动接口是先只有一个列表,点击列表项再发现请求,生成另外一个下拉选择, 需要这个三 ...

  6. 支付宝退款和结果查询接口简单实现(.Net 7.0)

    〇.前言 支付宝对 .Net 的支持还是比较充分的,在每个接口文档中都有关于 C# 语言的示例,这样就大大降低了对接的难度,很容易上手. 官方接口文档地址:退款-alipay.trade.refund ...

  7. 【转载】手动DIY制作机械臂

    相关链接: https://news.cnblogs.com/n/703664/ https://www.bilibili.com/video/BV12341117rG https://www.cnb ...

  8. [SDOI2010] 城市规划 题解

    前言 题目链接:洛谷. 题意简述 树套环上求至少间隔两个位置的最大独立集. (树套环,即树上每个结点都是一个结点或环) 题目分析 将题目拆解成树上 DP 和环上 DP 即可.用 tarjan 缩点就行 ...

  9. .NET 与 LayUI 实现高效敏捷开发框架

    前言 WaterCloud 是一个集成了 LayUI 的高效敏捷开发框架,专为 .NET 开发者设计. 它不仅支持多种 .NET 版本(.NET 4.5..NET Core 3.1..NET 5..N ...

  10. java-多线程(下)

    多线程简单入门(Java)(下篇:多线程Java中的使用) 目录 一.创建多线程 二.线程的安全 三.线程的通信 一.创建多线程 在Java中,多线程的创建有4种方式. 方式一:继承于Thread类; ...