一个合格C++程序员,应该善用智能指针!

科技   2024-12-18 12:02   北京  

START

Hi,大家好!今天我们来聊一聊C++中的智能指针。

在谈到学习C++时,好多人都说它特别难,说它复杂。很可能有一部分原因就是C++的内存管理,在程序运行过程中很容易就会出现内存泄漏。然而从C++11引入的智能指针这一问题得到解决。

C++11引入了三种智能指针:

  • std::shared_ptr

  • std::weak_ptr

  • std::unique_ptr

unsetunset1、std::shared_ptrunsetunset

std::shared_ptr 是用于管理动态分配的资源,实现自动内存管理。它是一个引用计数型智能指针,允许多个 shared_ptr 对象共享同一块动态分配的内存,并在不再需要时自动释放。

以下是 std::shared_ptr 的一些重要特点和用法:

  1. 引用计数: std::shared_ptr 使用引用计数来跟踪共享的资源的使用情况。每个 std::shared_ptr 对象都包含一个计数器,记录有多少个 std::shared_ptr 对象共享同一块内存。

  2. 安全性: std::shared_ptr 通过引用计数机制来确保在所有持有该资源的 std::shared_ptr 对象被销毁后,资源会被释放。这避免了内存泄漏和空悬指针等问题。

  3. 拷贝和赋值: std::shared_ptr 支持拷贝和赋值操作,当进行拷贝时,计数器会增加;当进行销毁或重新赋值时,计数器会减少。当计数器减少到 0 时,资源会被释放。

  4. 动态分配的资源: std::shared_ptr 通常用于管理动态分配的资源,如内存、文件句柄等。它不仅可以管理指针指向的内存,还可以管理自定义的资源,如自定义的释放器等。

  5. 线程安全性: std::shared_ptr 在多线程环境下是线程安全的,可以被多个线程同时访问和操作,不需要额外的同步机制。

使用 std::shared_ptr 可以有效地管理动态分配的资源,避免内存泄漏和空悬指针等问题,并且可以方便地进行资源的共享和传递。然而,要注意避免循环引用的问题,这可能导致资源无法释放。通常可以使用 std::weak_ptr 来解决循环引用的问题。

下面来看一个使用 std::shared_ptr 的简单示例:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor" << std::endl;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }
};

int main() {
    // 创建一个动态分配的 MyClass 对象,并用 shared_ptr 管理
    std::shared_ptr<MyClass> ptr1(new MyClass);

    // 创建另一个 shared_ptr,与 ptr1 共享同一块内存
    std::shared_ptr<MyClass> ptr2 = ptr1;

    // 使用箭头运算符访问对象成员函数
    ptr1->doSomething();

    // 当 ptr1 和 ptr2 被销毁时,资源会被自动释放
    return 0;
}

在这个示例中,我们首先创建了一个动态分配的 MyClass 对象,并用 std::shared_ptr 管理它。然后,我们创建了另一个 std::shared_ptr,与第一个 std::shared_ptr 共享同一块内存。这意味着两个 std::shared_ptr 对象共享同一个计数器和同一块内存。最后,我们通过箭头运算符访问了 MyClass 对象的成员函数,并且在程序结束时,由于 ptr1ptr2 被销毁,MyClass 对象的资源会被自动释放。

这个示例展示了 std::shared_ptr 的基本用法,包括对象的创建、拷贝、访问成员函数以及自动资源管理。

unsetunset2、std::weak_ptrunsetunset

std::weak_ptr 是用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 允许共享资源但不拥有它,它是 std::shared_ptr 的弱引用。

std::shared_ptr 不同,std::weak_ptr 并不增加引用计数,因此它不会影响资源的生命周期。当最后一个 std::shared_ptr 指向的资源被释放后,所有相关联的 std::weak_ptr 对象都会自动失效,指向空指针。

以下是 std::weak_ptr 的一些重要特点和用法:

  1. 弱引用: std::weak_ptr 是一个弱引用,它不增加资源的引用计数,因此不会影响资源的生命周期。

  2. 共享资源: std::weak_ptr 允许与 std::shared_ptr 共享同一块内存,但不拥有它。它通常用于解决循环引用的问题,防止资源无法释放。

  3. 检查是否有效: 可以使用 std::weak_ptrexpired() 方法来检查与之关联的资源是否有效。如果资源已经释放,则 expired() 返回 true,否则返回 false

  4. 获取强引用: 可以使用 std::weak_ptrlock() 方法来获取与之关联的资源的强引用(即 std::shared_ptr 对象)。如果资源仍然有效,则 lock() 返回一个有效的 std::shared_ptr;如果资源已经释放,则返回一个空的 std::shared_ptr

以下是一个示例,展示了 std::weak_ptr 的基本用法:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor" << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> weakPtr = sharedPtr;

    // 检查 weakPtr 是否有效
    if (!weakPtr.expired()) {
        // 获取强引用
        std::shared_ptr<MyClass> strongPtr = weakPtr.lock();
        if (strongPtr) {
            // 访问资源
            std::cout << "Resource is valid." << std::endl;
        }
    }

    // sharedPtr 被销毁,资源释放
    sharedPtr.reset();

    // 再次检查 weakPtr 是否有效
    if (weakPtr.expired()) {
        std::cout << "Resource is expired." << std::endl;
    }

    return 0;
}

在这个示例中,我们首先创建了一个 std::shared_ptr 来管理动态分配的资源,然后创建了一个 std::weak_ptr 对象与之共享同一块内存。我们使用 expired() 方法检查了 std::weak_ptr 是否有效,并使用 lock() 方法获取了与之关联的资源的强引用。最后,我们释放了 sharedPtr,并再次检查了 std::weak_ptr 是否有效。

通过使用 std::weak_ptr,我们可以解决 std::shared_ptr 的循环引用问题,确保资源能够正确释放,避免内存泄漏。

unsetunset3、std::unique_ptrunsetunset

std::unique_ptr 是用于管理动态分配的资源。与 std::shared_ptr 不同,std::unique_ptr 是独占所有权的智能指针,即一个 std::unique_ptr 对象独占一个动态分配的资源,并负责在其生命周期结束时释放该资源。

以下是 std::unique_ptr 的一些重要特点和用法:

  1. 独占所有权: std::unique_ptr 是独占所有权的智能指针,一次只能有一个 std::unique_ptr 对象拥有一个动态分配的资源。

  2. 资源所有权转移: std::unique_ptr 支持资源所有权的转移。可以通过 std::move 函数将一个 std::unique_ptr 对象的所有权转移到另一个对象。

  3. 禁止拷贝和赋值: std::unique_ptr 对象禁止拷贝和赋值操作。这意味着不能对 std::unique_ptr 对象进行拷贝构造或赋值操作,从而确保资源的独占性。

  4. 动态分配的资源: std::unique_ptr 通常用于管理动态分配的资源,如内存、文件句柄等。它不仅可以管理指针指向的内存,还可以管理自定义的资源,如自定义的释放器等。

  5. 移动语义: std::unique_ptr 支持移动语义,可以高效地将资源转移给其他 std::unique_ptr 对象。这意味着在传递 std::unique_ptr 参数时,不会发生资源的拷贝,而是发生所有权的转移。

以下是一个示例,展示了 std::unique_ptr 的基本用法:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor" << std::endl;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }
};

int main() {
    // 创建一个动态分配的 MyClass 对象,并用 unique_ptr 管理
    std::unique_ptr<MyClass> ptr(new MyClass);

    // 调用对象的成员函数
    ptr->doSomething();

    // 当 ptr 被销毁时,资源会被自动释放
    return 0;
}

在这个示例中,我们首先创建了一个动态分配的 MyClass 对象,并用 std::unique_ptr 管理它。然后,我们通过箭头运算符调用了 MyClass 对象的成员函数,并且在程序结束时,由于 ptr 被销毁,MyClass 对象的资源会被自动释放。

通过使用 std::unique_ptr,我们可以方便地管理动态分配的资源,并避免内存泄漏和空悬指针等问题。

END

作者:YuLinMuRong
来源:Linux兵工厂

版权归原作者所有,如有侵权,请联系删除

推荐阅读
ChatGPT全球宕机,我的天塌了!
预热两年半的STM32N6,到底有哪些亮点?
一个高效、安全、可靠的串口通讯开源方案

→点关注,不迷路←

嵌入式微处理器
关注嵌入式相关技术和资讯,你想知道的都在这里。
 最新文章