当前位置:   article > 正文

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

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

一、引言

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

二、简单的原理演示

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

  1. // 简单的智能指针
  2. #include <iostream>
  3. template <typename T>
  4. class MyPointer{
  5. private:
  6. T* pointer_;
  7. public:
  8. MyPointer(T *p): pointer_(p){
  9. }
  10. ~MyPointer(){
  11. delete pointer_;
  12. std::cout << "pointer_ destroy.";
  13. }
  14. };
  15. int main(){
  16. MyPointer<int> p(new int(10));
  17. } // 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

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

访问 std::unique_ptr 指向的对象

  1. #include <iostream>
  2. #include <memory>
  3. int main() {
  4. std::unique_ptr<int> ptr(new int(10));
  5. std::cout << *ptr << std::endl; // 输出 10
  6. return 0;
  7. }

转移 std::unique_ptr 的所有权

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

注意事项

  • 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

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

访问 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 管理的同一个对象。

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

使用 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智能指针避免循环引用

  1. #include <iostream>
  2. #include <memory>
  3. // 定义两个类,它们会相互引用对方
  4. class B; // 前向声明
  5. class A {
  6. public:
  7. std::shared_ptr<B> bPtr; // 使用shared_ptr来持有B的引用
  8. ~A() {
  9. std::cout << "A is being destroyed\n";
  10. }
  11. };
  12. class B {
  13. public:
  14. std::weak_ptr<A> aWeakPtr; // 使用weak_ptr来避免循环引用
  15. ~B() {
  16. std::cout << "B is being destroyed\n";
  17. }
  18. };
  19. int main() {
  20. // 创建两个shared_ptr,分别指向A和B的实例
  21. auto aPtr = std::make_shared<A>();
  22. auto bPtr = std::make_shared<B>();
  23. // 让A持有B的shared_ptr
  24. aPtr->bPtr = bPtr;
  25. // 让B持有A的weak_ptr,从而避免循环引用
  26. bPtr->aWeakPtr = aPtr;
  27. // 此时,没有直接的循环引用,因为B持有的是A的weak_ptr
  28. // 离开main函数的作用域时,aPtr和bPtr会被销毁
  29. // 由于bPtr持有的是aPtr的weak_ptr,它不会阻止aPtr的销毁
  30. // 当aPtr被销毁时,它会销毁其持有的bPtr(因为bPtr是一个shared_ptr)
  31. // 然后bPtr的销毁会触发B的析构函数,接着是A的析构函数(如果它还没有被销毁的话)
  32. return 0;
  33. }
  34. // 输出结果应该是:
  35. // B is being destroyed
  36. // A is being destroyed
  37. // 注意:输出的顺序可能因编译器和运行时环境的不同而有所变化

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/1021443
推荐阅读
相关标签
  

闽ICP备14008679号