前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >智能指针详解

智能指针详解

作者头像
Andromeda
发布2023-10-21 11:33:13
3020
发布2023-10-21 11:33:13
举报
文章被收录于专栏:Andromeda的专栏

为什么引入智能指针?

内存泄漏问题

C++在堆上申请内存后,需要手动对内存进行释放。随着代码日趋复杂和协作者的增多,很难保证内存都被正确释放,因此很容易导致内存泄漏。

在上述代码中,FunctionWithMemoryLeak()函数动态分配了一个整型对象的内存,并在结束时没有释放该内存。这就导致了内存泄漏,因为没有机制来释放这块分配的内存。

代码语言:javascript
复制
void FunctionWithMemoryLeak() {
    int* ptr = new int(5);  // 在堆上动态分配内存
    // 没有释放内存,造成内存泄漏
}

int main() {
    FunctionWithMemoryLeak();
    // ...
    return 0;
}

多线程下的对象析构问题

在多线程环境下,对象的析构问题需要特别注意,因为多个线程可能同时访问和操作同一个对象。如果多个线程同时尝试析构同一个对象,可能会导致对象被多次删除。因此稍有不慎就会导致程序崩溃。

代码语言:javascript
复制
#include <iostream>
#include <thread>
using namespace std;

class Resource {
public:
    Resource() {
        cout << "Resource acquired." << endl;
    }
    ~Resource() {
        cout << "Resource released." << endl;
    }
};

void ThreadFunc(Resource* resource) {
    // 模拟对资源的使用
    this_thread::sleep_for(std::chrono::seconds(2));
    cout << "Thread: Using resource." << endl;
    // 在多线程环境下,资源的析构可能由其他线程执行
    delete resource;
}

int main() {
    Resource* sharedResource = new Resource();
    thread t(ThreadFunc, sharedResource);
    // 在主线程中,早期销毁了资源
    delete sharedResource;
    t.join();
    cout << "Main: Finished." << endl;
    return 0;
}

在上述代码中,我们创建了一个共享资源Resource的实例,并在主线程和另一个线程中对其进行操作。主线程在启动另一个线程后早期销毁了资源,而另一个线程仍在使用已经销毁的资源。这会导致未定义行为,访问无效的内存,可能导致崩溃或数据损坏。

而智能指针设计的初衷就是可以帮助我们管理堆上申请的内存,即开发者只需要申请,释放内存的任务交给智能指针。用于确保程序不存在内存和资源泄漏且是异常安全的。

智能指针的使用

下面是一个原始指针和智能指针比较的示例代码

代码语言:javascript
复制
// 原始指针
void rawptr(){
    // 使用原始指针
    Obj *rawptr = new Obj("raw pointer");
    // dosomething
    rawptr->doSomething();
    // 释放内存
    delete rawptr;
}

// 智能指针
void smtptr(){
    // 使用智能指针
    unique_ptr<Obj> smtptr(new Obj("smart pointer"));
    // dosomething
    smtptr->doSomething();
    // 自动释放资源
}

智能指针通过封装指向堆分配对象的原始指针,并提供自动的内存管理和资源释放机制,帮助避免内存泄漏和资源管理错误。

智能指针的特点包括:

  1. 拥有权管理:智能指针拥有其所指向的对象,负责在适当的时机释放内存。这意味着当智能指针超出作用域或不再需要时,它会自动调用析构函数来释放内存。
  2. 析构函数处理:智能指针的析构函数中通常包含了对所拥有对象的内存释放操作,确保在智能指针被销毁时,关联的资源也会被释放。这种自动化的资源管理有助于避免内存泄漏和资源泄漏。
  3. 异常安全性:智能指针在异常情况下能够保证资源的正确释放。即使发生异常,智能指针也会在其作用域结束时被销毁,并调用析构函数来释放资源。

智能指针封装了指向堆分配对象的原始指针,因此智能指针通常提供直接访问其原始指针的方法。 C++ 标准库智能指针拥有一个用于此目的的get成员函数。

代码语言:javascript
复制
// 智能指针
void smtptr(){
    // 使用智能指针
    unique_ptr<Obj> smtptr(new Obj("smart pointer"));
    // dosomething
    smtptr->doSomething();
    // 获取智能指针的原始指针
    Obj *obj = pLarge.get()
    // 自动释放资源
}

智能指针的类型

目前C++11主要支持的智能指针为以下几种:

  • unique_ptr
  • shared_ptr
  • weak_ptr

unique_ptr

std::unique_ptr是 C++ 标准库提供的智能指针之一,用于管理动态分配的对象。它提供了独占所有权的语义,即同一时间只能有一个std::unique_ptr拥有对对象的所有权。当std::unique_ptr被销毁或重置时,它会自动释放所拥有的对象,并回收相关的内存。

std::unique_ptr支持所有权的转移,可以通过move将一个std::unique_ptr实例的所有权转移到另一个实例。这种所有权转移可以通过移动构造函数和移动赋值运算符来实现。

代码语言:javascript
复制
// 创建一个 std::unique_ptr
unique_ptr<Resource> uResource1 = make_unique<Resource>();
// 使用移动构造函数将所有权转移到另一个 std::unique_ptr
unique_ptr<Resource> uResource2 = move(uResource1);

可以通过下图来描述

其基本用法

代码语言:javascript
复制
unique_ptr<Obj> a1(new Obj());
// 获取原始指针
Obj *raw_a = a1.get();

/*
std::unique_ptr 类型提供了一个名为 operator bool() 的成员函数,
用于将 std::unique_ptr 对象转换为布尔值。
该函数用于检查 std::unique_ptr 是否持有有效的指针
*/
if(a1)
{
    // a1 拥有指针
}
// release释放所管理指针的所有权,返回原生指针。下面两条语句等价
unique_ptr<Obj> a2(a1.release());
a2 = move(a1);
// reset释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针
a2.reset(new Obj());
// 没有参数,以下两条语句等价
a2.reset();
//释放并销毁原有对象
a2 = nullptr;

参考官方文档:如何:创建和使用 unique_ptr 实例

shared_ptr

std::shared_ptr用于管理动态分配的对象。与std::unique_ptr不同,std::shared_ptr允许多个智能指针共享对同一个对象的所有权,通过引用计数来跟踪资源的使用情况。当最后一个std::shared_ptr对象销毁时,资源会被释放。也就是说多个std::shared_ptr可以拥有同一个原生指针的所有权。

在初始化一个shared_ptr之后,可以复制它,将其分配给其他shared_ptr实例。 所有实例均指向同一个对象,并共享资源与一个控制块。每当新的shared_ptr添加、超出范围或重置时增加和减少引用计数,当引用计数达到零时,控制块将删除内存资源和自身。

可以通过下图来描述

代码语言:javascript
复制
// 第一次创建内存资源时,请使用make_shared
auto sptr = make_shared<Obj>()
shared_ptr<Obj> a1(new Obj());
// 与unique_ptr不同,允许共享
shared_ptr<Obj> a2 = a1;
// 获得原生指针
Obj *raw_a = a1.get();
/*
std::unique_ptr 类型提供了一个名为 operator bool() 的成员函数,
用于将 std::unique_ptr 对象转换为布尔值。
该函数用于检查 std::unique_ptr 是否持有有效的指针
*/
if(a1)
{
    // a1 拥有指针
}
// 如果引用计数为 1,则返回true,否则返回false
if(a1.unique())
{
    // 如果返回true,引用计数为1
}
// use_count() 返回引用计数的大小
int cnt = a1.use_count();

参考官方文档:如何:创建和使用 shared_ptr 实例

weak_ptr

循环引用的情况是指两个或多个std::shared_ptr对象相互持有对方的所有权,形成死锁,导致引用计数无法降为零,从而std::shared_ptr无法被释放造成内存泄漏。

std::weak_ptr用于解决std::shared_ptr可能引发的循环引用和内存泄漏问题。std::weak_ptr允许跟踪一个由std::shared_ptr管理的对象,而不会增加引用计数。它本身是一个弱指针,所以它本身是不能直接调用原生指针的方法的。如果想要使用原生指针的方法,需要将其先转换为一个std::shared_ptr

weak_ptr可以通过一个shared_ptr创建。

代码语言:javascript
复制
shared_ptr<Obj> a1(new Obj());
weak_ptr<Obj> weak_a1 = a1; //不增加引用计数,避免循环引用
// expired()判断所指向的原生指针是否被释放,如果被释放了返回true,否则返回false
if(weak_a1.expired())
{
    //如果为true,weak_a1对应的原生指针已经被释放了
}
// 返回原生指针的引用计数
int cnt = weak_a1.use_count();
/* 
lock()返回shared_ptr,如果原生指针没有被释放,
则返回一个非空的shared_ptr,否则返回一个空的shared_ptr
*/
if(shared_ptr<Obj> shared_a = weak_a1.lock())
{
    //此时可以通过shared_a进行原生指针的方法调用
}
//将weak_a1置空
weak_a1.reset();

参考官方文档:如何:创建和使用 weak_ptr 实例

智能指针使用实践

writing

参考文章:C++ 智能指针最佳实践&源码分析

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么引入智能指针?
    • 内存泄漏问题
      • 多线程下的对象析构问题
      • 智能指针的使用
      • 智能指针的类型
        • unique_ptr
          • shared_ptr
            • weak_ptr
            • 智能指针使用实践
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档