C++智能指针的用法

简介: C++智能指针的用法



1. 智能指针

智能指针是一种在C++中用于管理动态分配内存的工具,它们提供了更安全和方便的方式来管理内存资源,以避免内存泄漏和资源管理错误。

常用的智能指针类型包括:

  1. std::shared_ptr共享指针,用于多个智能指针共享相同的资源,引用计数方式来管理资源的生命周期。当最后一个引用离开作用域时,资源被释放。
  2. std::unique_ptr唯一指针,表示独占所有权的指针,不能被复制或共享。当 std::unique_ptr 离开作用域时,它拥有的资源会被自动释放。
  3. std::weak_ptr弱指针,用于协助 std::shared_ptr 来解决循环引用问题。它不增加引用计数,不影响资源的生命周期,但可以用于检查资源是否仍然有效。

区别

std::shared_ptr:

  1. 允许多个 std::shared_ptr 共享同一块内存。
  2. 跟踪引用计数,当最后一个 std::shared_ptr 对象离开作用域时,它会自动释放内存。
  3. 可以使用 std::make_shared 创建对象并返回一个 std::shared_ptr。
  4. 适用于共享资源的情况,例如多个对象需要访问同一块内存。
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

std::unique_ptr:

  1. 拥有唯一所有权,不能被复制。当 std::unique_ptr 离开作用域时,它会自动释放内存。
  2. 没有引用计数,通常比 std::shared_ptr 更快。
  3. 可以使用 std::make_unique 创建对象并返回一个 std::unique_ptr。
  4. 适用于独占资源的情况,例如动态分配的对象。
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);

std::weak_ptr:

  1. 用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 不能直接访问所管理的内存,需要将其转换为 std::shared_ptr 才能访问内存。
  2. 本身无引用计数,但为了协助 std::shared_ptr 进行引用计数管理而存在的。
  3. 不提供 std::make_weak 函数。通常与 std::shared_ptr 一起使用,用于避免循环引用
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
std::shared_ptr<int> sharedAgain = weak.lock();  // 将 weak 转换为 shared_ptr

总结:

  • std::shared_ptr 适用于多个智能指针需要共享同一块内存的情况 / 可以使用 std::make_shared 创建对象并返回一个 std::shared_ptr / 跟踪引用计数
  • std::unique_ptr 适用于独占资源的情况,通常更高效 / 可以使用 std::make_unique 创建对象并返回一个 std::unique_ptr / 没有引用计数,通常比 std::shared_ptr 更快。
  • std::weak_ptr 用于解决循环引用问题,通常与 std::shared_ptr 配合使用 / 不提供 std::make_weak 函数,通常与 std::shared_ptr 一起使用 / 本身无引用计数,但为了协助 std::shared_ptr 进行引用计数管理而存在的。

2. 优势

  1. 自动内存管理:智能指针自动处理资源的分配和释放,减少了手动管理内存的需求。这有助于防止内存泄漏和释放已释放的内存,提高了程序的稳定性。
  2. 安全性:智能指针提供了更安全的资源管理方式,减少了内存管理错误的发生,如悬挂指针、重复释放等。它们有助于消除许多常见的编程错误。
  3. 生命周期管理std::shared_ptr 使用引用计数来管理资源的生命周期,确保只有在不再被引用时才会释放资源。这有助于避免在资源仍然在使用时释放它。
  4. 简化代码:使用智能指针可以简化代码,因为它们自然地表达了资源的所有权和生命周期。这提高了代码的可读性和可维护性。
  5. 自动资源释放std::unique_ptr 保证资源的独占所有权,当它超出作用域时会自动释放资源。这有助于确保资源不会泄漏。
  6. 共享资源std::shared_ptr 允许多个智能指针共享相同的资源,这可以减少内存使用,同时确保资源在不再被引用时被释放。

3. 用法

3.1 std::shared_ptr共享指针

std::shared_ptr是 C++ 标准库中的一个智能指针,用于管理动态分配的对象的生命周期。允许多个 shared_ptr 实例共享同一个对象,当最后一个 shared_ptr 实例离开作用域时,将自动释放分配的资源。

1. 使用需导入头文件

#include <memory>

2. 创建 std::shared_ptr:示例创建了一个 std::shared_ptr,并将其初始化为一个整数类型的动态分配对象,该对象的值为 42。std::make_shared 是一个创建 std::shared_ptr 的便捷函数,它分配内存并返回一个智能指针。

std::shared_ptr<int> sharedInt = std::make_shared<int>(42);

3. 共享 std::shared_ptr:sharedInt 和 anotherSharedInt 指向相同的整数对象,它们共享资源。

std::shared_ptr<int> anotherSharedInt = sharedInt;

4. 访问共享的对象:通过解引用 std::shared_ptr,你可以访问共享对象的值,就像使用原始指针一样。

int value = *sharedInt;
int anotherValue = *anotherSharedInt;

5. 自动资源管理

当 std::shared_ptr 没有引用时,它会自动释放分配的资源,无需手动释放内存。这可以有效地避免内存泄漏。

sharedInt.reset(); // 释放 sharedInt 指向的资源

6. 检查引用计数

std::shared_ptr 使用引用计数来跟踪有多少个 std::shared_ptr 实例共享资源。可以使用 use_count() 方法来检查引用计数:

int count = sharedInt.use_count();

示例程序

#include <iostream>
#include <memory>
int main() {
    // 创建一个 std::shared_ptr,共享整数对象
    std::shared_ptr<int> sharedInt = std::make_shared<int>(42);
    // 共享相同的对象
    std::shared_ptr<int> anotherSharedInt = sharedInt;
    // 访问共享的对象
    int value = *sharedInt;
    int anotherValue = *anotherSharedInt;
    std::cout << "sharedInt: " << value << std::endl;
    std::cout << "anotherSharedInt: " << anotherValue << std::endl;
    // 检查引用计数
    int count = sharedInt.use_count();
    std::cout << "Reference count: " << count << std::endl;
    // 释放 sharedInt 指向的资源
    sharedInt.reset();
    // 再次检查引用计数
    count = anotherSharedInt.use_count();
    std::cout << "Reference count after reset: " << count << std::endl;
    return 0;
}

示例中,创建了两个 std::shared_ptr 实例,它们都指向相同的整数对象。我们访问了这两个智能指针,然后释放了一个智能指针的资源。最后检查了引用计数以验证资源的释放。这个示例展示了 std::shared_ptr 如何自动管理资源,确保资源在不再需要时被正确释放。

3.2 std::unique_ptr唯一指针

std::unique_ptr 是 C++ 标准库中的另一个智能指针类,用于管理动态分配的对象,但与 std::shared_ptr 不同,std::unique_ptr 确保同一时刻只有一个指针可以拥有对动态对象的唯一所有权,因此它适用于独占所有权的情况。当 std::unique_ptr 超出范围或被显式地释放时,它将自动释放分配的资源。

1. 包含头文件

#include <memory>

2. 创建 std::unique_ptr:示例使用 std::make_unique 创建 std::unique_ptr,并将其初始化为一个整数类型的动态分配对象,该对象的值为 42。

std::unique_ptr<int> uniqueInt = std::make_unique<int>(42);

3. 唯一所有权:示例将 uniqueInt 的所有权转移给 anotherUniqueInt,因为 std::unique_ptr 确保同一时刻只有一个指针拥有对动态对象的唯一所有权。

std::unique_ptr<int> anotherUniqueInt = std::move(uniqueInt);

4. 访问唯一的对象:可以像使用原始指针一样解引用 std::unique_ptr,以访问唯一的对象。

int value = *anotherUniqueInt;

5. 自动资源管理:std::unique_ptr 在超出范围时或被显式释放时,会自动释放分配的资源,无需手动释放内存。

anotherUniqueInt.reset(); // 释放 anotherUniqueInt 指向的资源

6. 检查是否为空:可以使用条件语句来检查 std::unique_ptr 是否为空,即是否指向有效的对象。

if (!anotherUniqueInt) {
    std::cout << "anotherUniqueInt is null" << std::endl;
}

示例程序:示例中std::unique_ptr 确保了对整数对象的唯一所有权,并在合适的时候释放了资源,避免了内存泄漏。

#include <iostream>
#include <memory>
int main() {
    // 创建一个 std::unique_ptr,拥有整数对象的唯一所有权
    std::unique_ptr<int> uniqueInt = std::make_unique<int>(42);
    // 唯一所有权转移
    std::unique_ptr<int> anotherUniqueInt = std::move(uniqueInt);
    // 访问唯一的对象
    int value = *anotherUniqueInt;
    std::cout << "anotherUniqueInt: " << value << std::endl;
    // 释放 anotherUniqueInt 指向的资源
    anotherUniqueInt.reset();
    // 检查是否为空
    if (!anotherUniqueInt) {
        std::cout << "anotherUniqueInt is null" << std::endl;
    }
    return 0;
}

3.3 std::weak_ptr弱指针

std::weak_ptr 是 C++ 标准库中的另一种智能指针类,它用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 允许你观察 std::shared_ptr 指向的对象,但不拥有该对象,因此不会增加引用计数,也不会影响对象的生存期。这对于解决对象之间相互引用导致的内存泄漏问题非常有用。

1. 包含头文件

#include <memory>

2. 创建 std::shared_ptr 和 std::weak_ptr:示例创建了 std::shared_ptr 来管理整数对象,并使用 std::weak_ptr 来观察该对象。

std::shared_ptr<int> sharedInt = std::make_shared<int>(42);
std::weak_ptr<int> weakInt = sharedInt;

3. 使用 std::weak_ptr

if (auto shared = weakInt.lock()) {
    // 使用 shared 来访问对象
    int value = *shared;
    std::cout << "Weak pointer is valid: " << value << std::endl;
} else {
    std::cout << "Weak pointer is expired" << std::endl;
}

使用 std::weak_ptr 的 lock() 方法,可以尝试将其转换为一个有效的 std::shared_ptr。如果 std::weak_ptr 指向的对象仍然存在,lock() 将返回一个有效的 std::shared_ptr,否则返回一个空的 std::shared_ptr。

4. 弱引用的对象释放

std::shared_ptr 所有权结束后,std::weak_ptr 不会阻止对象的释放。

sharedInt.reset(); // 释放 sharedInt 指向的资源
if (auto shared = weakInt.lock()) {
    // shared 现在为空
    std::cout << "Weak pointer is still valid: " << *shared << std::endl;
} else {
    std::cout << "Weak pointer is expired" << std::endl;
}

即使 sharedInt 被释放,std::weak_ptr 仍然可以安全地检查是否指向一个有效的对象。

总之,std::weak_ptr 是一种用于解决 std::shared_ptr 循环引用问题的智能指针,它允许观察共享对象,但不拥有对象的所有权,从而避免了内存泄漏。

示例程序

#include <iostream>
#include <memory>
int main() {
    // 创建一个 std::shared_ptr 来管理整数对象
    std::shared_ptr<int> sharedInt = std::make_shared<int>(42);
    // 创建一个 std::weak_ptr 来观察 sharedInt
    std::weak_ptr<int> weakInt = sharedInt;
    // 使用 std::weak_ptr 来访问对象
    if (auto shared = weakInt.lock()) {
        int value = *shared;
        std::cout << "Weak pointer is valid: " << value << std::endl;
    } else {
        std::cout << "Weak pointer is expired" << std::endl;
    }
    // 释放 sharedInt 的资源
    sharedInt.reset();
    // 再次使用 std::weak_ptr
    if (auto shared = weakInt.lock()) {
        std::cout << "Weak pointer is still valid: " << *shared << std::endl;
    } else {
        std::cout << "Weak pointer is expired" << std::endl;
    }
    return 0;
}

示例中,std::weak_ptr 允许观察 sharedInt,并在释放 sharedInt 后仍然可以安全地检查其有效性。

目录
相关文章
|
2天前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
|
11天前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
14 3
|
1月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。
|
1月前
|
C++
C++(九)this指针
`this`指针是系统在创建对象时默认生成的,用于指向当前对象,便于使用。其特性包括:指向当前对象,适用于所有成员函数但不适用于初始化列表;作为隐含参数传递,不影响对象大小;类型为`ClassName* const`,指向不可变。`this`的作用在于避免参数与成员变量重名,并支持多重串联调用。例如,在`Stu`类中,通过`this-&gt;name`和`this-&gt;age`明确区分局部变量与成员变量,同时支持链式调用如`s.growUp().growUp()`。
|
2月前
|
编译器 C++ 容器
【C++】String常见函数用法
【C++】String常见函数用法
17 1
|
2月前
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
24 1
|
2月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
40 0
|
2月前
|
存储 C++
c++学习笔记06 指针
C++指针的详细学习笔记06,涵盖了指针的定义、使用、内存占用、空指针和野指针的概念,以及指针与数组、函数的关系和使用技巧。
30 0
|
17天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
60 30
|
6天前
|
并行计算 Unix Linux
超级好用的C++实用库之线程基类
超级好用的C++实用库之线程基类
12 4