在 C++ 中,智能指针(Smart Pointers)是用来自动管理动态分配内存的工具,可以减少内存泄漏的风险。C++标准库提供了几种常见的智能指针,包括 std::unique_ptrstd::shared_ptrstd::weak_ptr,它们各自有不同的特性和用途。下面我们将详细对比解释这些智能指针。

std::unique_ptr

std::unique_ptr 是一种独占性的智能指针,意味着同一时刻只有一个 unique_ptr 可以拥有某个对象的所有权。它遵循 RAII(Resource Acquisition Is Initialization)原则,==当 unique_ptr 被销毁时,它所管理的对象会被自动释放。==
特点

  • 独占所有权:同一个对象只能被一个 unique_ptr 拥有。
  • 不能拷贝:==unique_ptr 不能被复制,因为它拥有唯一的所有权。尝试拷贝会导致编译错误。==
  • 支持移动语义:可以通过移动构造函数或者移动赋值操作符==将所有权从一个 unique_ptr 移动到另一个==。(?)
  • 自动释放内存:当 unique_ptr 被销毁时,它会自动释放所管理的对象。
    • 注意结合 std:: make_unique 和 std:: move 使用!
      #include <iostream>
      #include <memory>
      
      class MyClass {
      public:
          MyClass(int val) : value(val) {
              std::cout << "MyClass constructed with value: " << value << std::endl;
          }
          ~MyClass() {
              std::cout << "MyClass destructed with value: " << value << std::endl;
          }
          void showValue() const {
              std::cout << "Value: " << value << std::endl;
          }
      
      private:
          int value;
      };
      
      int main() {
          std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10);  // ptr1 拥有 MyClass 的所有权
          std::cout << "Moving ownership to ptr2..." << std::endl;
          std::unique_ptr<MyClass> ptr2 = std::move(ptr1);  // 转移所有权
          
          // ptr1 现在为空,不能再访问对象
          if (ptr1) {
              ptr1->showValue();
          } else {
              std::cout << "ptr1 is now null." << std::endl;
          }
      
          // ptr2 现在拥有 MyClass 对象的所有权
          ptr2->showValue();
      
          return 0;
      }
      

      std::shared_ptr

      std::shared_ptr 是一种共享所有权的智能指针,多个 shared_ptr 可以共享同一个对象的所有权。只有当最后一个 shared_ptr 被销毁时,所管理的对象才会被释放。
      特点
  • 共享所有权:多个 shared_ptr 可以指向同一个对象,==每个 shared_ptr 都会增加一个引用计数。==
  • 引用计数shared_ptr 会跟踪有多少个 shared_ptr 指向同一个对象,当引用计数归零时,对象会被自动销毁。
  • 拷贝与赋值shared_ptr 支持拷贝和赋值操作,每次拷贝都会增加引用计数。
  • 性能开销:由于引用计数的管理,shared_ptr 会带来额外的性能开销(例如原子操作)。

    释放需要手动吗?

    #include <iostream>
    #include <memory>
    
    class MyClass {
    public:
        MyClass(int x) : x_(x) {
            std::cout << "MyClass(" << x_ << ") constructed\n";
        }
        ~MyClass() {
            std::cout << "MyClass(" << x_ << ") destructed\n";
        }
    
    private:
        int x_;
    };
    
    int main() {
        std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(10);
        {
            std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
            std::cout << "Inside inner scope\n";
        } // ptr2 离开作用域,引用计数减少
        std::cout << "Outside inner scope\n";
        return 0;
    }

    说明:

  • ptr1ptr2 共享对同一个 MyClass 对象的所有权。
  • ptr2 离开作用域时,它的引用计数减少,但对象不会立即销毁,因为 ptr1 仍然指向它。
  • ptr1 离开作用域时,引用计数归零,MyClass 对象被销毁。
    问题:shared_ptr 引起循环计数问题:
    #include <iostream>
    #include <memory>
    
    class A;
    
    class B {
    public:
        std::shared_ptr<A> a_ptr;
        B() {
            std::cout << "B constructed\n";
        }
        ~B() {
            std::cout << "B destructed\n";
        }
    };
    
    class A {
    public:
        std::shared_ptr<B> b_ptr;
        A() {
            std::cout << "A constructed\n";
        }
        ~A() {
            std::cout << "A destructed\n";
        }
    };
    
    int main() {
        {
            std::shared_ptr<A> a = std::make_shared<A>();
            std::shared_ptr<B> b = std::make_shared<B>();
            
            a->b_ptr = b; // A -> B
            b->a_ptr = a; // B -> A
    
            // A 和 B 形成循环引用,内存无法释放
        } // 这里的 a 和 b 应该销毁,但因为循环引用,它们的引用计数永远不为 0
    
        std::cout << "End of scope\n";
        return 0;
    }
    

    std::weak_ptr

    std::weak_ptr 是一种弱引用的智能指针,它与 shared_ptr 配合使用,用来避免循环引用。weak_ptr 不会改变引用计数,它只提供对 shared_ptr 所管理对象的访问。如果对象被销毁,weak_ptr 就不再有效。
    特点
  • 不影响引用计数weak_ptr 不会增加或减少引用计数,它只是一个“观察者”。
  • 用于打破循环引用weak_ptr 主要用于解决 shared_ptr 引起的循环引用问题。例如,==当两个对象通过 shared_ptr 相互引用时,会导致内存无法释放。使用 weak_ptr 可以避免这种情况。==
  • 转换为 shared_ptrweak_ptr 可以通过 lock() 方法转换为 shared_ptr。如果对象已经被销毁,lock() 返回一个空的 shared_ptr
    使用 weak_ptr 打破循环引用:
    #include <iostream>
    #include <memory>
    
    class A;
    
    class B {
    public:
        std::weak_ptr<A> a_ptr;  // 使用 weak_ptr 避免循环引用
        B() {
            std::cout << "B constructed\n";
        }
        ~B() {
            std::cout << "B destructed\n";
        }
    };
    
    class A {
    public:
        std::shared_ptr<B> b_ptr;
        A() {
            std::cout << "A constructed\n";
        }
        ~A() {
            std::cout << "A destructed\n";
        }
    };
    
    int main() {
        {
            std::shared_ptr<A> a = std::make_shared<A>();
            std::shared_ptr<B> b = std::make_shared<B>();
            
            a->b_ptr = b; // A -> B
            b->a_ptr = a; // B -> A,但这里是 weak_ptr,避免了循环引用
    
            // 由于没有循环引用,内存可以正确释放
        } // A 和 B 都会被销毁
    
        std::cout << "End of scope\n";
        return 0;
    }
    

    对比总结

特性std::unique_ptrstd::shared_ptrstd::weak_ptr
所有权管理独占所有权共享所有权不拥有对象,辅助管理共享所有权
拷贝行为不支持拷贝,只能移动支持拷贝,增加引用计数不影响引用计数,只是弱引用
引用计数无引用计数有引用计数,自动销毁对象无引用计数,仅观察对象是否仍存在
适用场景需要独占所有权,简单的对象管理需要共享所有权的复杂对象管理解决 shared_ptr 引起的循环引用问题
性能开销最小,只有对象本身的管理稍大,包含引用计数的管理开销极小,仅提供观察者功能