智能指针详解

智能指针详解

为什么引入智能指针?

内存泄漏问题

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

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

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

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

多线程下的对象析构问题

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

#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的实例,并在主线程和另一个线程中对其进行操作。主线程在启动另一个线程后早期销毁了资源,而另一个线程仍在使用已经销毁的资源。这会导致未定义行为,访问无效的内存,可能导致崩溃或数据损坏。

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

智能指针的使用

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

// 原始指针
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成员函数。

// 智能指针
void Smtptr(){
    // 使用智能指针
    unique_ptr<Obj> smtptr(new Obj("smart pointer"));
    // dosomething
    smtptr->doSomething();
    // 获取智能指针的原始指针
    Obj *obj = smtptr.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实例的所有权转移到另一个实例。这种所有权转移可以通过移动构造函数和移动赋值运算符来实现。

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

可以通过下图来描述

图片[1]-智能指针详解-能不能吃完饭再说

其基本用法

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添加、超出范围或重置时增加和减少引用计数,当引用计数达到零时,控制块将删除内存资源和自身。

可以通过下图来描述

图片[2]-智能指针详解-能不能吃完饭再说

// 第一次创建内存资源时,请使用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

class ObjB;

class ObjA {
public:
    std::shared_ptr<ObjB> objB;
};

class ObjB {
public:
    std::shared_ptr<ObjA> objA;
};

int main() {
    std::shared_ptr<ObjA> a = std::make_shared<ObjA>();
    std::shared_ptr<ObjB> b = std::make_shared<ObjB>();
    
    a->objB = b;
    b->objA = a;
    
    // 循环引用,对象无法被正确释放
    return 0;
}

weak_ptr可以通过一个shared_ptr创建。

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++ 智能指针最佳实践&源码分析

------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞22赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片