在 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 引起的循环引用问题
性能开销最小,只有对象本身的管理稍大,包含引用计数的管理开销极小,仅提供观察者功能