前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++的智能指针unique_ptr、shared_ptr和weak_ptr

C++的智能指针unique_ptr、shared_ptr和weak_ptr

作者头像
叶茂林
发布2023-07-30 15:53:59
3870
发布2023-07-30 15:53:59
举报

C++的智能指针是一种特殊的指针类型,它能够自动管理内存资源,避免常见的内存泄漏和多次释放等问题。C++11引入了三种主要的智能指针:unique_ptr、shared_ptr和weak_ptr。

①unique_ptr

在C++中,unique_ptr是一个智能指针(smart pointer)类模板,用于管理动态分配的内存资源,它提供了自动释放内存的功能。与原始指针相比,unique_ptr有更高的安全性和易用性。

unique_ptr具有以下特点:

独占所有权:每个unique_ptr实例拥有对其所指向对象的唯一所有权。这意味着在任何时候只有一个unique_ptr可以指向一个特定的对象。

自动释放内存:当unique_ptr超出作用域或被重新赋值时,它所管理的内存会自动释放。这样就避免了内存泄漏的问题。

指针语义:unique_ptr的使用方式与原始指针相似,可以通过指针操作符(->)和解引用操作符(*)来访问所指向对象的成员。

不可拷贝:unique_ptr是不可拷贝的,即不能进行复制构造和赋值操作。这是为了确保独占所有权的特性,防止多个指针同时管理同一个对象的内存。

支持移动语义:unique_ptr支持移动构造和移动赋值操作,可以将所有权转移给新的unique_ptr,而无需进行内存拷贝。

可自定义删除器:unique_ptr可以通过模板参数来指定一个删除器(deleter)函数对象,用于在释放内存时执行额外的清理操作。

示例代码:

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

int main() {
    // 创建一个unique_ptr,指向一个动态分配的int对象
    std::unique_ptr<int> ptr(new int(42));

    // 使用指针操作符和解引用操作符访问所指向对象的值
    std::cout << *ptr << std::endl;  // 输出: 42

    // 通过移动构造函数将所有权转移给另一个unique_ptr
    std::unique_ptr<int> ptr2 = std::move(ptr);

    // 注意,此时ptr已经为空指针,不再拥有对象的所有权
    std::cout << *ptr2 << std::endl;  // 输出: 42

    // 使用自定义删除器
    struct Deleter {
        void operator()(int* p) {
            std::cout << "Custom deleter called" << std::endl;
            delete p;
        }
    };

    std::unique_ptr<int, Deleter> ptr3(new int(100), Deleter());

    // unique_ptr超出作用域时会自动释放内存,同时调用自定义删除器
    return 0;
}

常见成员函数

operator*:解引用操作符,用于获取 unique_ptr 所指向对象的引用。

operator->:箭头操作符,用于通过 unique_ptr 访问对象的成员函数或成员变量。

get:返回指向所管理对象的裸指针。

reset:重置 unique_ptr,释放当前所管理的对象并接管新的对象。

release:释放对所管理对象的控制权,并返回该指针的裸指针。

swap:交换两个 unique_ptr 的内容。

②shared_ptr

在C++中,shared_ptr是一个智能指针(smart pointer)类模板,用于管理动态分配的内存资源。与unique_ptr相比,shared_ptr可以实现多个指针共享同一块内存,并且提供了自动释放内存的功能。

shared_ptr具有以下特点:

共享所有权:多个shared_ptr实例可以同时指向同一个对象,它们共享对所指向对象的所有权。只有当所有shared_ptr都超出作用域或被重新赋值时,才会释放所管理的内存。

自动释放内存:当最后一个指向对象的shared_ptr超出作用域或被重新赋值时,它会自动释放所管理的内存。这种机制称为引用计数(reference counting),通过计数器来追踪当前有多少个shared_ptr指向同一块内存。

指针语义:shared_ptr的使用方式与原始指针相似,可以通过指针操作符(->)和解引用操作符(*)来访问所指向对象的成员。

可拷贝:shared_ptr是可拷贝的,即可以进行复制构造和赋值操作。每次拷贝会增加引用计数。当引用计数变为0时,表示没有任何shared_ptr指向该内存,会释放内存。

循环引用问题:如果存在循环引用(两个或多个对象相互持有shared_ptr),会导致内存泄漏。为了解决这个问题,可以使用弱引用指针weak_ptr。

示例代码:

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

int main() {
    // 创建一个shared_ptr,指向一个动态分配的int对象
    std::shared_ptr<int> ptr1(new int(42));

    // 使用指针操作符和解引用操作符访问所指向对象的值
    std::cout << *ptr1 << std::endl;  // 输出: 42

    // 复制构造函数,共享同一块内存
    std::shared_ptr<int> ptr2 = ptr1;

    // 增加引用计数
    std::cout << ptr1.use_count() << std::endl;  // 输出: 2

    // 通过弱引用指针weak_ptr解决循环引用问题
    std::weak_ptr<int> weakPtr = ptr1;

    // 使用lock()函数获取一个shared_ptr
    std::shared_ptr<int> ptr3 = weakPtr.lock();
    if (ptr3 != nullptr) {
        // 成功获取shared_ptr
    }

    // 减少引用计数
    ptr1.reset();

    return 0;
}

常见成员函数

operator*:解引用操作符,用于获取 shared_ptr 所指向对象的引用。

operator->:箭头操作符,用于通过 shared_ptr 访问对象的成员函数或成员变量。

get:返回指向所管理对象的裸指针。

reset:重置 shared_ptr,释放当前所管理的对象并接管新的对象。

release:释放对所管理对象的控制权,并返回该指针的裸指针。

swap:交换两个 shared_ptr 的内容。

use_count:返回当前被所有 shared_ptr 指向的对象的引用计数。

③weak_ptr

在 C++ 中,weak_ptr 是一种智能指针(smart pointer),用于解决循环引用问题。它是由 shared_ptr 派生而来,但不会增加引用计数,只是对所指向对象进行观察,并不拥有对象的所有权。

循环引用问题

循环引用问题指的是在使用shared_ptr管理对象时,存在两个或多个对象相互持有shared_ptr,形成一个循环引用的情况。这种情况下,每个对象的引用计数都不会变为0,导致内存泄漏。

具体来说,当两个对象相互持有shared_ptr时,它们的引用计数始终大于0,因此它们所指向的内存块永远不会被释放。即使程序使用结束,这部分内存也无法回收,造成了内存泄漏的问题。

循环引用问题的实际场景可能是两个对象之间存在双向关联,比如A对象持有shared_ptr指向B对象,而B对象也持有shared_ptr指向A对象。当这两个对象的生命周期延长,超过了程序实际需要它们的时间时,就会造成循环引用和内存泄露。

为了解决循环引用问题,C++中引入了弱引用指针weak_ptr。弱引用指针和shared_ptr不同,它不会增加引用计数,只是对所指向对象进行观察,并不拥有对象的所有权。通过弱引用指针,我们可以在需要时使用lock()函数获取一个有效的shared_ptr来操作对象,一旦对象的引用计数变为0,弱引用指针将自动失效。

使用弱引用指针可以破坏循环引用,让所有的shared_ptr都能够正常析构并释放所管理的内存,避免了潜在的内存泄漏风险。

weak_ptr 具有以下特点和用法

弱引用:因为 weak_ptr 不会增加引用计数,所以当所有 shared_ptr 都释放后,weak_ptr 将自动失效。它允许你观察一个对象,但不影响其生命周期。

通过 shared_ptr 创建:通常,我们使用 shared_ptr 来初始化 weak_ptr。这样可以确保 weak_ptr 观察的对象仍然存在。

使用 lock() 获取 shared_ptr:要操作 weak_ptr 所观察的对象,可以使用 lock() 函数获取一个有效的 shared_ptr。如果原始的 shared_ptr 已经被释放,lock() 返回一个空的 shared_ptr。

判断是否有效:可以使用 expired() 函数来检查 weak_ptr 是否已经失效,即所观察的 shared_ptr 是否已经被释放。

解决循环引用问题:由于 weak_ptr 不增加引用计数,可以用于解决两个或多个对象之间的循环引用问题,避免内存泄漏。

示例代码:

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

class B; // 前置声明

class A {
public:
    std::shared_ptr<B> bPtr;

    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> aWeakPtr;

    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> aPtr = std::make_shared<A>();
    std::shared_ptr<B> bPtr = std::make_shared<B>();

    aPtr->bPtr = bPtr;
    bPtr->aWeakPtr = aPtr;

    std::cout << "Use count of aPtr: " << aPtr.use_count() << std::endl;
    std::cout << "Use count of bPtr: " << bPtr.use_count() << std::endl;

    // 使用 lock() 获取有效的 shared_ptr
    std::shared_ptr<A> lockedAPtr = bPtr->aWeakPtr.lock();
    if (lockedAPtr) {
        // 使用 lockedAPtr 操作所观察的对象
        std::cout << "A exists" << std::endl;
    } else {
        std::cout << "A does not exist" << std::endl;
    }

    return 0;
}

常见成员函数

expired:检查 weak_ptr 所观察的 shared_ptr 是否已经失效。

lock:获取一个有效的 shared_ptr,用于操作所观察的对象。如果原始的 shared_ptr 已经被释放,返回一个空的 shared_ptr。

use_count:返回当前被所有 shared_ptr 指向的对象的引用计数。

reset:重置 weak_ptr。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ①unique_ptr
  • ②shared_ptr
  • ③weak_ptr
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档