C++11 shared_ptr 与 make_shared源码剖析详解

yizhihongxing

C++11中的shared_ptr和make_shared是两个非常实用的特性,能够帮助我们更好地管理内存。本文将深入介绍shared_ptr和make_shared的实现原理,帮助读者更好地掌握这两个特性。

1. shared_ptr简介

shared_ptr是C++11提供的一种智能指针,用于管理动态内存。它可以自动对内存进行引用计数,并在引用计数为0时自动释放内存。shared_ptr使用起来非常方便,并且可以避免内存泄漏。

shared_ptr可以像原生指针一样进行赋值、比较和解引用,但它会自动进行引用计数。因此,多个shared_ptr对象可以指向同一个内存块,同时对它进行引用计数,当引用计数为0时自动释放内存。

2. make_shared简介

make_shared是C++11提供的一种智能指针构造函数,它可以更快速、更安全地构造shared_ptr对象。make_shared会在内存中分配一个新的对象,并且将这个对象封装在一个shared_ptr内部。这个新的对象会自动被初始化为默认值(或者按照提供的参数进行初始化)。

使用make_shared可以节省内存、提高效率,并且可以避免内存泄漏。

3. shared_ptr的实现原理

shared_ptr的实现原理比较复杂,主要包括内存管理、引用计数等。下面我们来详细介绍一下。

3.1 内存管理

当我们使用shared_ptr来管理一个指针时,它会在堆上分配一块内存来存储引用计数、指针等信息。同时,它也会在堆上分配一块内存来存储我们需要管理的对象。这两块内存会被绑定在一起,组成一个shared_ptr对象。

在这个过程中,我们需要注意内存泄露的问题。如果我们将一个原生指针赋值给一个shared_ptr对象时,这个原生指针所指向的内存块就不再由我们手动管理了。如果我们在后面的代码中使用这个原生指针,就有可能出现内存泄漏的情况。

3.2 引用计数

当我们使用shared_ptr来管理一个对象时,每个shared_ptr对象都会包含一个计数器,用于记录当前有多少个shared_ptr对象引用了这个对象。在shared_ptr对象的拷贝构造函数、拷贝赋值运算符、析构函数等操作中,都需要进行引用计数的操作。

当一个新的shared_ptr对象被创建时,它的计数为1。当它被拷贝构造时,它的计数会加1;当它被拷贝赋值时,它的计数会减1(可能会降至0,此时需要释放内存);当它被销毁时,它的计数会减1(可能会降至0,此时需要释放内存)。

3.3 循环引用问题

当两个shared_ptr对象相互引用时,就会出现循环引用的问题。这种情况下,两个shared_ptr对象的计数都不会降至0,因此它们所指向的内存块永远不会被释放。这就会导致内存泄漏的问题。

为了解决循环引用的问题,我们可以使用weak_ptr。weak_ptr也是一种智能指针,但它不会对对象进行引用计数。也就是说,当我们使用weak_ptr来引用对象时,它不会增加对象的引用计数。因此,当一个对象被多个shared_ptr对象引用时,我们可以使用weak_ptr来解决循环引用的问题。

4. make_shared的实现原理

make_shared的实现原理比shared_ptr更加复杂,但它也更加高效。下面我们来介绍一下make_shared的实现原理。

4.1 内存管理

make_shared会在内存中分配一个新的对象,并且将这个对象封装在一个shared_ptr内部。这个新的对象会自动被初始化为默认值(或者按照提供的参数进行初始化)。

为了实现这个功能,make_shared需要使用到C++11的可变参数模板。它可以将任意多个参数转化为一个变长的参数列表,然后将这个参数列表传递给一个模板类。

4.2 内存分配

在make_shared的实现中,我们需要进行内存分配并进行初始化操作。为了提高效率,我们可以将内存分配和初始化操作合并在一起,使用一个malloc函数来完成它们。

malloc函数是C++标准库中的一个函数,用于在堆上分配一块内存。使用malloc函数可以避免内存泄漏的问题,同时也可以提高效率。

4.3 构造shared_ptr对象

在make_shared的实现中,我们需要构造一个shared_ptr对象,并将它指向新分配的对象。这个过程涉及到shared_ptr的构造函数和虚函数表等内容。

当我们使用shared_ptr来管理一个对象时,它会在堆上分配一块内存来存储引用计数、指针等信息。同时,它也会在堆上分配一块内存来存储我们需要管理的对象。这两块内存会被绑定在一起,组成一个shared_ptr对象。

在make_shared的实现中,我们需要使用一个新分配的对象来替换旧的shared_ptr对象。为了完成这个操作,我们需要使用到C++11中的emplace函数。这个函数可以将一个元素加入到容器中,并用元素的构造函数进行初始化操作。

5. 示例说明

下面我们来介绍两个使用shared_ptr和make_shared的示例。这些示例可以帮助你更好地理解这两个特性。

5.1 shared_ptr示例

#include <memory>
#include <iostream>

class Foo {
public:
    void bar() { std::cout << "Hello, world!" << std::endl; }
};

int main() {
    std::shared_ptr<Foo> p1(new Foo);
    std::shared_ptr<Foo> p2 = p1;

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

    p1->bar();  // 输出Hello, world!
    p2->bar();  // 输出Hello, world!

    return 0;
}

在这个示例中,我们使用了shared_ptr来管理一个对象。我们创建了两个shared_ptr对象p1和p2,它们都指向同一个对象。当p1和p2被销毁时,它们会自动释放对内存的引用。

5.2 make_shared示例

#include <memory>
#include <iostream>

class Foo {
public:
    Foo() { std::cout << "Foo()" << std::endl; }
    Foo(int i) { std::cout << "Foo(" << i << ")" << std::endl; }
};

int main() {
    std::shared_ptr<Foo> p1(new Foo);
    std::shared_ptr<Foo> p2 = std::make_shared<Foo>();

    std::shared_ptr<Foo> p3 = std::make_shared<Foo>(42);

    return 0;
}

在这个示例中,我们使用了make_shared来构造一个对象。我们创建了三个shared_ptr对象p1、p2和p3,并使用了不同的方式来构造它们。当p1、p2和p3被销毁时,它们会自动释放对内存的引用。其中,p2和p3的构造方式更加高效。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++11 shared_ptr 与 make_shared源码剖析详解 - Python技术站

(0)
上一篇 2023年5月23日
下一篇 2023年5月23日

相关文章

  • C#语言主要特性总结

    C#语言主要特性总结 C#是由微软开发的一种面向对象编程语言,拥有以下主要特性: 1. 强类型语言 C#是一种强类型语言,它要求变量在使用前必须定义类型。这意味着,对于一个变量,编译器需要确切地知道变量的类型,才能确定它占用多少内存空间。 以下是C#中的强类型定义示例: int num = 42; //定义一个int类型的变量 string name = &…

    C 2023年5月22日
    00
  • C++模拟实现vector的示例代码

    下面是“C++模拟实现vector的示例代码”的攻略: 1. 了解vector的基本概念 在实现vector之前,首先需要了解vector的基本概念。vector是C++标准模板库中的一个容器,可以存储任意类型的数据,并且支持动态扩展。在使用vector时,需要包含 <vector> 头文件,并且使用 std 命名空间。 2. 分析vector的…

    C 2023年5月22日
    00
  • 如何统计在一篇文章中某个单词出现了几次,以及第一次出现的位置

    以下是一个完整的攻略,用于统计一篇文章中某个单词出现的次数和第一次出现的位置。 1. 获取文本数据 首先,需要从文章中获取文本数据。如果文章已经存储在文件中,可以使用文件读取函数来获取文本数据。如果文章存储在数据库中,可以使用数据库查询功能来获取文本数据。在这里,我们假设文本数据已经被保存到一个字符串变量中,并且该变量名为text。 2. 统计单词出现次数 …

    C 2023年5月23日
    00
  • C++11如何引入的尾置返回类型

    C++11 引入了尾置返回类型(trailing return type)来增强类型推导的能力。尾置返回类型是一种特殊的语法形式,可用于在函数定义中指定函数返回类型。尾置返回类型的语法形式是在函数形参列表之后使用箭头(->),后跟将函数返回类型所需的任何内容。 尾置返回类型是在编译时确定的,因此使用尾置返回类型可以提高代码的可读性和可维护性,例如可以维…

    C 2023年5月23日
    00
  • Clion下vcpkg的使用详解

    Clion是一个流行的C++开发环境,而vcpkg是一个C++库管理器,旨在简化依赖项安装和管理。本文将简要介绍Clion中如何使用vcpkg。 安装vcpkg 首先,需要下载并安装vcpkg,可以从https://github.com/microsoft/vcpkg上下载最新版本的vcpkg。 接下来,我们需要将vcpkg添加到系统路径中,然后打开命令行工…

    C 2023年5月23日
    00
  • 一文带你深入了解C++中的类型转换

    一文带你深入了解C++中的类型转换 在C++中,类型转换是一种将一种数据类型转换为另一种数据类型的方法。类型转换在编程中非常常见,它可以将我们需要的数据类型作为参数传递给函数或表达式,也可以帮助我们处理特定的数据类型。 类型转换的分类 在C++中,类型转换可以分为隐式类型转换和显式类型转换两种: 隐式类型转换:自动将一种数据类型转换为另一种数据类型。例如,将…

    C 2023年5月24日
    00
  • C++实现动态规划过程详解

    C++实现动态规划过程详解 什么是动态规划 动态规划是一种通过把问题划分为相互重叠的子问题来解决复杂问题的算法。它的主要思想是将原问题分解为一些子问题,通过计算和储存子问题的答案来逐步推导出原问题的解。通常用于解决最优化问题。 动态规划有很多经典的问题,在实际工程中也有很多应用。C++是一种常用的编程语言,下面就是C++实现动态规划的过程详解。 动态规划过程…

    C 2023年5月23日
    00
  • MySQL中json字段的操作方法

    当MySQL版本大于等于5.7.8时,支持json类型的字段。json是具有可读性和结构的数据格式,MySQL提供了方便的函数和操作符来处理json数据。下面将详细讲解MySQL中json字段的操作方法。 创建json类型的字段 在MySQL中创建json类型的字段,可以使用以下语法: CREATE TABLE table_name ( id INT PRI…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部