忘记delete会造成内存泄漏。用智能指针便可以解决这类问题。智能指针主要用于管理在将普通的指针封装为一个对象。因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

unique_ptr

   unique_ptr持有对对象的独有权,只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr指针离开作用域时,若其指向对象,则其所指对象销毁。

     将一个unique_ptr赋值给另一个时(p4 = p3),编译器会阻止这种行为,因为它会使得原unique_ptr(p3)失去对原始对象的所有权,导致原始对象可能无法正确释放引发资源泄露

   unique_ptr提供了对右值引用的支持。当一个unique_ptr对象作为临时右值进行赋值操作时,这是被允许的。

pu3 = unique_ptr<string>(new string ("You"));

临时创建的unique_ptr会在所有权转移给pu3之后立即销毁,这样就不会有悬挂的智能指针。

   若要安全地转移unique_ptr的所有权,可以使用std::move()函数。

ps2 = move(ps1);

语句会将ps1所拥有的资源的所有权转移给ps2。转移后ps1虽然仍然存在,但不再拥有任何资源,若直接访问可能会导致未定义行为。

shared_ptr

  shared_ptr使用引用计数  ,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。

  初始化:通过构造函数(传入指针)赋值,std::make_shared<T>进行初始化。

#include <iostream>
#include <future>
#include <thread>
using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
   shared_ptr<Person> p1(new Person(1));// Person(1)的引用计数为1
   shared_ptr<Person> p2=make_shared<Person>(2);
   p1.reset(new Person(3));// 首先生成新对象,然后引用计数减1,引用计数为0。
                             //故析构Person(1)
                            // 最后将新对象的指针交给智能指针
    shared_ptr<Person> p3 = p1;//现在p1和p3同时指向Person(3),Person(3)的引用计数为2
    p1.reset();//Person(3)的引用计数为1
    p3.reset();//Person(3)的引用计数为0,析构Person(3)
    return 0;
}

不能将一个原始指针直接赋值给一个智能指针(一个是类,一个是指针)

std::shared_ptr<int> p4 = new int(1);// error

reset()

转移所有权,包含两个操作。当智能指针中有值的时候,调用reset()会使引用计数减1。当调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将就对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针。

get()

获取原始指针

std::shared_ptr<int> p4(new int(5)); int *pInt = p4.get();

shared_ptr注意

不要用一个原始指针初始化多个shared_ptr,原会造成二次销毁。

int *p5 = new int; std::shared_ptr<int> p6(p5); std::shared_ptr<int> p7(p5); //logic error

    当使用原始指针 new 出来的内存地址去初始化多个 std::shared_ptr 时,如果重复使用同一份原始指针,会导致多个 shared_ptr 共享同一块内存,并各自维护一份引用计数。

    这样一来,当任何一个 shared_ptr 被销毁时,它会减少引用计数,并在引用计数变为0时释放这块内存。然而,如果有多个 shared_ptr 都认为自己拥有这块内存的所有权,那么在第一个 shared_ptr 销毁并释放内存后,其他 shared_ptr 依然持有对这块已释放内存的引用,再次尝试销毁时就会发生逻辑错误,为“悬挂指针”。

int *p5 = new int;
std::shared_ptr<int> p6(p5); // 第一个 shared_ptr 初始化,内存引用计数为 1
std::shared_ptr<int> p7(p5); // 第二个 shared_ptr 也尝试初始化,内存引用计数变为 2

// 当 p6 或 p7 的生命周期结束,它们的析构函数会被调用,引用计数减1
// 当引用计数变为0时,内存会被释放一次
// 但是如果 p6 和 p7 都结束了生命周期,那么内存会被释放两次,造成逻辑错误

     为了避免这种情况,正确的做法是使用 std::shared_ptr 的构造函数或 std::make_shared 函数来共享同一块内存,而不是直接用原始指针初始化多个 shared_ptr。如果确实需要多个 shared_ptr 共享同一块内存,应使用 std::shared_ptr 的拷贝构造函数或 std::shared_ptr::reset 函数来转移所有权,而不是直接初始化。

std::shared_ptr<int> p8 = std::make_shared<int>(42); // 使用 make_shared 创建并初始化
std::shared_ptr<int> p9 = p8; // 正确的做法:通过拷贝构造函数共享同一块内存
int *p5 = new int(100);
std::shared_ptr<int> p6(p5); // 创建第一个 shared_ptr
std::shared_ptr<int> p7;     // 创建第二个 shared_ptr,初始为空
// 使用 reset 函数转移所有权
p7.reset(p6.get()); 
// 或者使用拷贝构造函数
// std::shared_ptr<int> p7(p6);
// 现在 p6 和 p7 共享同一块内存,它们的引用计数都为 1

(2)避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。解决方法是AStruct或BStruct改为weak_ptr。

struct AStruct;
struct BStruct;

struct AStruct {
    std::shared_ptr<BStruct> bPtr;
    ~AStruct() { cout << "AStruct is deleted!"<<endl; }
};

struct BStruct {
    std::shared_ptr<AStruct> APtr;
    ~BStruct() { cout << "BStruct is deleted!" << endl; }
};

void TestLoopReference()
{
    std::shared_ptr<AStruct> ap(new AStruct);
    std::shared_ptr<BStruct> bp(new BStruct);
    ap->bPtr = bp;
    bp->APtr = ap;
}

  首先创建了一个 std::shared_ptr<AStruct> 对象 ap,指向新建的 AStruct 实例。

  然后创建了一个 std::shared_ptr<BStruct> 对象 bp,指向新建的 BStruct 实例。

  接下来,将 bp 的智能指针赋值给 ap 中的 bPtr 成员,这样 ap 的 bPtr 持有了 bp,bp 的引用计数加1。最后,将 ap 的智能指针赋值给 bp 中的 APtr 成员,这样 bp 的 APtr 持有了 ap,ap 的引用计数也加1。此时,ap 和 bp 形成了循环引用。在常规情况下,由于双方互相持有对方的引用,各自的引用计数都不为0,所以在函数结束后,ap 和 bp 都不会被自动释放,导致内存泄露。

   C++11 之后的标准库中的 std::shared_ptr 已经处理了这种循环引用的问题,它使用了所谓的“弱引用”技术(weak reference counting)。在这种情况下,当任意一方的强引用计数降为0时,另一方持有的对该对象的引用将转换为弱引用,不再增加引用计数。因此,当 TestLoopReference 函数结束,ap 和 bp 的作用域结束时,它们的引用计数都会递减至0,最终触发两者的析构函数,分别输出 "BStruct is deleted!" 和 "AStruct is deleted!",并不会发生内存泄露。

当对象之间的拥有权不清楚的时候,weak_ptr并不能带来帮助。如果存在环,必须要找出来,然后手动打破。那怎么能够解决环形依赖呢?可以使用有完整垃圾回收机制的语言如Java,Go,Haskell,或者使用有些缺陷的垃圾回收器(C/C++)

weak_ptr

weak_ptr是用来指向shared_ptr的,用来判断shared_ptr指向的数据是否还存在。

通过lock()来判断是否存在了,lock()相当于

expired()?shared_ptr<element_type>() : shared_ptr<element_type>(*this)

当不存在的时候,会返回一个空的shared_ptr,weak_ptr在指向shared_ptr的时候,并不会增加ref count,因此weak_ptr主要有两个用途:

用来记录对象是否存在了。

 通过lock()来判断是否存在了,lock()相当于

expired()?shared_ptr<element_type>() : shared_ptr<element_type>(*this)

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.

expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false。

lock 用于获取所管理的对象的强引用(shared_ptr)。如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.

weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数。

share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

注意:不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();
p->print();

weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性。

weak_ptr 可以用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 weak_ptr,用于指向子类shared_ptr,这样基类仅仅观察自己的 weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。