C++中的智能指针介绍及使用

一、引言

        在软件开发与系统运维的广阔领域中,内存安全是基石亦是挑战。随着软件复杂度的增加,内存泄露、缓冲区溢出、越界访问等安全问题频发,不仅影响程序稳定性,更可能成为黑客攻击的入口。确保内存安全,意味着守护数据完整、预防系统崩溃,更是保障用户体验与业务连续性的关键。因此,深入探讨内存安全机制,采用先进防护技术,构建安全的内存使用环境,是每一位开发者与运维人员不可或缺的技能与责任。
        C/C++语言可以方便的操作内存(通过指针等),因此能得到较高的运行效率。而指针是一把双刃剑,因为指针导致的问题也层出不穷,成为一个不可以绕过的问题。关于解决这个问题,C++也一直在努力,今天我们就来看看C++11中新增的智能指针。
 

二、简单的原理演示

        C++中的智能指针其实是应用了“类对象出作用域后会自动调用其析构函数”的特点,将内存资源回收放在智能指针的析构函数中。以下是一个简单的自定义智能指针,旨在为了帮助大家理解其基本原理。

// 简单的智能指针
#include <iostream>

template <typename T>
class MyPointer{
	private:
		T* pointer_;
	public:
		MyPointer(T *p): pointer_(p){
		}
		~MyPointer(){
			delete pointer_;
			std::cout << "pointer_ destroy.";
		}
};

int main(){
	MyPointer<int> p(new int(10));
} // p对象会在这里析构,自动调用析构函数

三、unique_ptr智能指针

        std::unique_ptr 是 C++11 引入的一种智能指针,用于自动管理具有唯一所有权的动态分配的对象。所有的智能指针都在<memory>头文件中声明。
        std::unique_ptr 保证其指向的对象在 unique_ptr 被销毁时自动被删除,从而避免了内存泄漏。与 std::shared_ptr 不同,std::unique_ptr 不可被复制(即,不能有两个 unique_ptr 指向同一个对象),但可以被移动(move),这允许 unique_ptr 的所有权在不同作用域之间转移。

基本用法
创建 std::unique_ptr

#include <memory>  
  
int main() {  
    // 使用 new 分配的内存初始化 unique_ptr  
    std::unique_ptr<int> ptr(new int(10));  
  
    // 使用 std::make_unique (C++14 及以后)  
    auto ptr2 = std::make_unique<int>(20);  
  
    return 0;  
}

访问 std::unique_ptr 指向的对象

#include <iostream>  
#include <memory>  
  
int main() {  
    std::unique_ptr<int> ptr(new int(10));  
    std::cout << *ptr << std::endl; // 输出 10  
    return 0;  
}

转移 std::unique_ptr 的所有权

#include <iostream>  
#include <memory>  
  
void take_ownership(std::unique_ptr<int> ptr) {  
    std::cout << *ptr << std::endl; // 原始 ptr 将不再拥有对象  
}  
  
int main() {  
    std::unique_ptr<int> ptr(new int(10));  
    take_ownership(std::move(ptr)); // 将所有权转移给函数  
    // 此时 ptr 为空,访问 *ptr 将是未定义行为  
    return 0;  
}

注意事项

  • std::unique_ptr 不能被复制(即没有拷贝构造函数和拷贝赋值运算符),但可以被移动(通过移动构造函数和移动赋值运算符)。
  •  当 std::unique_ptr 被销毁时,它指向的对象也会被删除。因此,确保 std::unique_ptr 指向的对象支持删除操作(即对象是通过 new 分配的)。
  • 使用 std::make_unique 是创建 std::unique_ptr 的推荐方式,因为它更安全且更简洁(不需要显式调用 new,也避免了与 new 相关的异常安全性问题)。
  • std::unique_ptr 是管理动态分配资源(特别是那些具有唯一所有权的资源)的强大工具,它帮助减少了内存泄漏的风险,并使代码更加简洁和安全。

四、shared_ptr智能指针

        std::shared_ptr 是 C++11 引入的另一种智能指针,用于自动管理具有共享所有权的动态分配的对象。与 std::unique_ptr 不同,std::shared_ptr 允许多个 shared_ptr 实例共享对同一对象的所有权。当最后一个 shared_ptr 被销毁或重置时,指向的对象才会被删除。这种机制通过内部使用一个控制块(通常是一个引用计数)来实现,该控制块跟踪有多少个 shared_ptr 实例指向某个对象。
基本用法
创建 std::shared_ptr

#include <memory>  
#include <iostream>  
  
int main() {  
    // 使用 new 分配的内存和 std::shared_ptr 构造函数  
    std::shared_ptr<int> ptr1(new int(10));  
  
    // 使用 std::make_shared(推荐方式)  
    auto ptr2 = std::make_shared<int>(20);  
  
    std::cout << *ptr1 << std::endl; // 输出 10  
    std::cout << *ptr2 << std::endl; // 输出 20  
  
    return 0;  
}

访问 std::shared_ptr 指向的对象
与 std::unique_ptr 类似,你可以通过解引用 shared_ptr 来访问其指向的对象。

std::cout << *ptr1 << std::endl; // 访问 ptr1 指向的对象

复制 std::shared_ptr
与 std::unique_ptr 不同,std::shared_ptr 支持复制操作,这会增加引用计数。

std::shared_ptr<int> ptr3 = ptr1; // 复制 ptr1,引用计数增加

        转移 std::shared_ptr 的所有权(实际上是复制,因为所有权是共享的)
        虽然 std::shared_ptr 可以通过移动语义进行转移,但在大多数情况下,这更像是复制操作,因为所有权是共享的。然而,你可以使用 std::move 来提高效率(尽管在 shared_ptr 的情况下,这种效率提升可能很小或不存在)。

auto ptr4 = std::move(ptr1); // 移动语义,但所有权仍然是共享的

注意事项

  • 当最后一个 std::shared_ptr 被销毁或重置时,指向的对象才会被删除。
  • std::shared_ptr 允许复制,因此可以安全地在多个地方共享对同一对象的所有权。
  • 使用 std::make_shared 是创建 std::shared_ptr 的推荐方式,因为它比直接使用 new 和 std::shared_ptr 构造函数更高效(因为它可以在单个内存分配中同时分配控制块和对象)。
  • 循环引用是 std::shared_ptr 使用时需要避免的一个常见问题。当两个或多个 shared_ptr 实例相互持有对方的引用时,它们会导致引用计数永远不会降至零,从而阻止对象被删除。解决循环引用的常见方法是使用 std::weak_ptr。

五、weak_ptr智能指针
 

        std::weak_ptr 是 C++11 引入的一种智能指针,用于解决 std::shared_ptr 可能导致的循环引用问题。std::weak_ptr 不拥有其所指向对象的所有权,即它不会增加对象的引用计数。相反,它提供了一种方式来访问一个由 std::shared_ptr 管理的对象,但不参与该对象的生命周期管理。

基本用法
创建 std::weak_ptr

std::weak_ptr 通常是通过一个 std::shared_ptr 来创建的,这样它就指向了由 std::shared_ptr 管理的同一个对象。

#include <memory>  
#include <iostream>  
  
int main() {  
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);  
    std::weak_ptr<int> weakPtr = sharedPtr; // weakPtr 现在指向 sharedPtr 管理的对象  
  
    // 不能直接解引用 weakPtr,因为它不拥有对象  
    // std::cout << *weakPtr << std::endl; // 这是错误的  
  
    // 但可以通过 lock 方法尝试获取一个指向对象的 shared_ptr  
    if (auto lockedPtr = weakPtr.lock()) {  
        std::cout << *lockedPtr << std::endl; // 输出 10  
    }  
  
    return 0;  
}

使用 lock 方法
        std::weak_ptr 的 lock 方法尝试返回一个指向其所指对象的 std::shared_ptr。如果原始 shared_ptr 仍然存在(即它指向的对象尚未被删除),则 lock 方法会成功并返回一个指向该对象的 shared_ptr。如果原始 shared_ptr 已被销毁(即它指向的对象已被删除),则 lock 方法会返回一个空的 shared_ptr。

注意事项

  • std::weak_ptr 不拥有它所指向的对象的所有权,因此不会增加对象的引用计数。
  • 当最后一个 std::shared_ptr 被销毁时,它所管理的对象也会被删除,即使还有 std::weak_ptr 指向该对象。
  • 使用 std::weak_ptr 可以解决 std::shared_ptr 之间的循环引用问题,因为它允许你保持对对象的引用,但不阻止对象的删除。
  • 不能直接解引用 std::weak_ptr,因为它不保证它所指向的对象仍然存在。相反,应该使用 lock 方法来获取一个临时的 std::shared_ptr,然后对该 shared_ptr 进行操作。
  • 由于 std::weak_ptr 不拥有对象的所有权,因此它通常与 std::shared_ptr 一起使用,以提供对共享对象的非拥有性访问。


六、使用weak智能指针避免循环引用

#include <iostream>  
#include <memory>  
  
// 定义两个类,它们会相互引用对方  
class B; // 前向声明  
  
class A {  
public:  
    std::shared_ptr<B> bPtr; // 使用shared_ptr来持有B的引用  
    ~A() {  
        std::cout << "A is being destroyed\n";  
    }  
};  
  
class B {  
public:  
    std::weak_ptr<A> aWeakPtr; // 使用weak_ptr来避免循环引用  
    ~B() {  
        std::cout << "B is being destroyed\n";  
    }  
};  
  
int main() {  
    // 创建两个shared_ptr,分别指向A和B的实例  
    auto aPtr = std::make_shared<A>();  
    auto bPtr = std::make_shared<B>();  
  
    // 让A持有B的shared_ptr  
    aPtr->bPtr = bPtr;  
  
    // 让B持有A的weak_ptr,从而避免循环引用  
    bPtr->aWeakPtr = aPtr;  
  
    // 此时,没有直接的循环引用,因为B持有的是A的weak_ptr  
  
    // 离开main函数的作用域时,aPtr和bPtr会被销毁  
    // 由于bPtr持有的是aPtr的weak_ptr,它不会阻止aPtr的销毁  
    // 当aPtr被销毁时,它会销毁其持有的bPtr(因为bPtr是一个shared_ptr)  
    // 然后bPtr的销毁会触发B的析构函数,接着是A的析构函数(如果它还没有被销毁的话)  
  
    return 0;  
} 
// 输出结果应该是:  
// B is being destroyed  
// A is being destroyed  
// 注意:输出的顺序可能因编译器和运行时环境的不同而有所变化

        在这个示例中,A类持有一个指向B的std::shared_ptr,而B类持有一个指向A的std::weak_ptr。由于B不持有A的shared_ptr,因此不会增加A的引用计数,从而避免了循环引用。当main函数结束时,aPtr和bPtr(作为shared_ptr)会被销毁。由于bPtr持有aPtr的weak_ptr,它不会阻止aPtr的销毁。接着,aPtr的销毁会触发A的析构函数,并导致A持有的bPtr(A中的bPtr成员)也被销毁,进而触发B的析构函数。这样,两个对象都被正确地销毁了,没有发生内存泄漏。


七、小结

        智能指针是C++为了防止内存泄露的一种可行的手段,它解决了编程人员在内存分配和回收上的纠结。因此,在不极致追求效率的情况下(智能指针会增加一定的开销),建议使用智能指针。

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hn_tzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值