智能指针是存储指向动态分配(堆)对象指针的类,用于对指针所指向对象的内存进行管理。由于C++中内存管理极其不友好,所以对于复杂的程序,就需要智能指针帮助我们对内存进行管理以免造成内存泄漏。智能指针其实并不是指针,而是一个栈对象,其生命周期由编译器进行管理,当栈对象生命周期结束时,就会自动调用析构函数释放掉由它管理的堆内存,通过重载“operator->”操作符来获取所管理对象的引用,并以此来操作对象。

参考博客:https://www.cnblogs.com/tenosdoit/p/3456704.html


几种智能指针

auto_ptr(已弃用)

class Test
{
public:
    Test(string s)
    {
        str = s;
       cout<<"Test creat\n";
    }
    ~Test()
    {
        cout<<"Test delete:"<<str<<endl;
    }
    string& getStr()
    {
        return str;
    }
    void setStr(string s)
    {
        str = s;
    }
    void print()
    {
        cout<<str<<endl;
    }
private:
    string str;
};
 
 
int main()
{
    auto_ptr<Test> ptest(new Test("123"));
    ptest->setStr("hello ");
    ptest->print();
    ptest.get()->print();
    ptest.reset(new Test("123"));
    ptest->print();
    return 0;
}

输出结果

auto_ptr有一个比较令人诟病的特性,就是如果用来赋值的时候被用于赋值的智能指针将在赋值后指向空对象,即p1=p2之后,p1将接管p2所指向的对象,而p2则为null。如果想用auto_ptr用于函数传参时,智能指针将值传递给函数形参时值就置空了。

void testfunc(auto_ptr<Test> p){
    p->print();
    return;
}
 
int main()
{
    auto_ptr<Test> ptest(new Test("123"));
    ptest->setStr("hello ");
    ptest->print();
    testfunc(ptest);
    if(ptest.get()==NULL)
    cout<<"ptest is null"<<endl;
    return 0;
}

输出结果

unique_ptr

unique_ptr是一个独享所有权的智能指针,其中unique就说明了无法对它进行复制赋值操作(无法使两个unique_ptr指向相同的对象),但是可以进行移动构造或移动赋值(.move())。当unique_ptr所指向的对象本身被删除释放时,unique_ptr会使用特定的deleter释放它所指向的对象。
使用unique_ptr可以实现:

  • 为动态申请的内存提供异常安全
  • 能够将动态申请的内存的所有权传递给函数
  • 从某个函数中返回对动态申请内存的所有权
  • 使用容器进行保存(由于容器中存在大量赋值传递操作,使用auto_ptr将造成大量的野指针)
int main()
{
    unique_ptr<Test> p1(new Test("123"));
    unique_ptr<Test> p2(new Test("456"));
    p2=std::move(p1);
    p2->print();
    return 0;
}

输出结果

可以看到p1在通过move把值赋给p2后,p2原来所指向的对象就被销毁了,p1将所有权移交给了p2,自身不再管理对象。

share_ptr

顾名思义可以用两个不同的share_ptr管理同一片动态申请内存,且能够使用=来进行赋值传递,通过成员函数use_count()对内存管理者进行计数,当有新的share_ptr来管理同一片动态申请内存时,计数器加1;当计数器为0时就释放掉所管理内存。

int main()
{
    shared_ptr<Test> p1(new Test("123"));
    cout<<"引用数:"<<p1.use_count()<<endl;
    shared_ptr<Test> p2=p1;
    cout<<"引用数:"<<p1.use_count()<<endl;
    p1.reset();
    cout<<"内存还没释放!"<<endl;
    p2.reset();
    return 0;
}

输出结果

weak_ptr

weak_ptr指向一个由shared_ptr管理的对象,但它并不影响对象的生命周期,即无法用它来对对象内存进行释放,用它来指向shared_ptr管理的对象引用计数器也不会加1。weak_ptr最大的用处还是协助shared_ptr管理内存,用来解决引用死锁(即循环引用问题)。

int main()
{
    shared_ptr<Test> p1(new Test("123"));
    cout<<"引用数:"<<p1.use_count()<<endl;
    weak_ptr<Test> p2=p1;
    cout<<"引用数:"<<p1.use_count()<<endl;
    return 0;
}

输出结果

用weak_ptr用来解决死锁问题,看如下例子:

class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
    {
        cout<<"A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout<<"B delete\n";
    }
};
 
void fun()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());
    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
}
 
int main()
{
    fun();
    return 0;
}

上面一段代码中,A引用了B,B引用了A,且两者都使用share_ptr管理内存。如果此时用share_ptr对A和B的对象进行管理,对于A、B的引用计数器将为2,当要跳出函数时,智能指针pa,pb析构使引用计数器均减1,但此时对象A和B中仍然保留着对对方的引用导致引用计数器无法减至0(即没有调用A和B的析构函数)导致内存没有被释放,从输出结果来看也确实如此。
析构函数没有被调用
此时只需将上面代码中A类或者B类中的share_ptr改成weak_ptr,则可以让其中一个引用计数器不会增加而只为1,最终其将在超出作用域时引用数减为0,内存被回收。

不能通过weak_ptr来直接访问对象的方法,需要先将weak_ptr转化成share_ptr

智能指针成员函数

  get() 获取所管理内存的地址
  reset(obj?) 重新设置管理的内存,将会释放原来管理的内存
  release() 返回管理内存的地址,并解除对所管理的内存的所有权
  use_account() 返回内存管理者的个数(shared_ptr,weak_ptr)
  lock() 将weak_ptr晋升为shared_ptr(shared_ptr<Test> p = p2.lock())
Last modification:September 4th, 2023 at 10:12 pm
If you think my article is useful to you, please feel free to appreciate