C++智能指针的用法

2024-02-20 13:17:02 浏览数 (1)

1. 智能指针

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

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

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

区别

std::shared_ptr:

允许多个 std::shared_ptr 共享同一块内存。

跟踪引用计数,当最后一个 std::shared_ptr 对象离开作用域时,它会自动释放内存。

可以使用 std::make_shared 创建对象并返回一个 std::shared_ptr。

适用于共享资源的情况,例如多个对象需要访问同一块内存。

代码语言:javascript复制
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

std::unique_ptr:

拥有唯一所有权,不能被复制。当 std::unique_ptr 离开作用域时,它会自动释放内存。

没有引用计数,通常比 std::shared_ptr 更快。

可以使用 std::make_unique 创建对象并返回一个 std::unique_ptr。

适用于独占资源的情况,例如动态分配的对象。

代码语言:javascript复制
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);

std::weak_ptr:

用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 不能直接访问所管理的内存,需要将其转换为 std::shared_ptr 才能访问内存。

本身无引用计数,但为了协助 std::shared_ptr 进行引用计数管理而存在的。

不提供 std::make_weak 函数。通常与 std::shared_ptr 一起使用,用于避免循环引用

代码语言:javascript复制
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. 使用需导入头文件

代码语言:javascript复制
#include <memory>

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

代码语言:javascript复制
std::shared_ptr<int> sharedInt = std::make_shared<int>(42);

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

代码语言:javascript复制
std::shared_ptr<int> anotherSharedInt = sharedInt;

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

代码语言:javascript复制
int value = *sharedInt;
int anotherValue = *anotherSharedInt;

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

代码语言:javascript复制
sharedInt.reset(); // 释放 sharedInt 指向的资源

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

代码语言:javascript复制
int count = sharedInt.use_count();

示例程序

代码语言:javascript复制
#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. 包含头文件

代码语言:javascript复制
#include <memory>

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

代码语言:javascript复制
std::unique_ptr<int> uniqueInt = std::make_unique<int>(42);

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

代码语言:javascript复制
std::unique_ptr<int> anotherUniqueInt = std::move(uniqueInt);

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

代码语言:javascript复制
int value = *anotherUniqueInt;

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

代码语言:javascript复制
anotherUniqueInt.reset(); // 释放 anotherUniqueInt 指向的资源

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

代码语言:javascript复制
if (!anotherUniqueInt) {
    std::cout << "anotherUniqueInt is null" << std::endl;
}

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

代码语言:javascript复制
#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. 包含头文件

代码语言:javascript复制
#include <memory>

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

代码语言:javascript复制
std::shared_ptr<int> sharedInt = std::make_shared<int>(42);
std::weak_ptr<int> weakInt = sharedInt;

3. 使用 std::weak_ptr

代码语言:javascript复制
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 不会阻止对象的释放。

代码语言:javascript复制
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 循环引用问题的智能指针,它允许观察共享对象,但不拥有对象的所有权,从而避免了内存泄漏。

示例程序

代码语言:javascript复制
#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 后仍然可以安全地检查其有效性。

0 人点赞