欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++基础之智能指针auto_ptr/unique_ptr/shared_ptr/weak_ptr

程序员文章站 2022-06-02 13:56:51
...

auto_ptr,unique_ptr,shared_ptr和weak_ptr

C ++库提供以下类型的智能指针实现:

  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr

它们都在内存头文件中声明。

auto_ptr

从C ++ 11开始,不推荐使用此类模板。unique_ptr是一个具有类似功能但具有更高安全性的新设施。
auto_ptr是一个智能指针,用于管理通过new表达式获取的对象,并在auto_ptr本身被销毁时删除该对象。
使用auto_ptr类描述的对象,它存储指向单个已分配对象的指针,该对象确保当它超出范围时,它指向的对象必须自动销毁。它基于独占所有权模型,即同一类型的两个指针不能同时指向同一资源。如下面的程序所示,复制或指定指针会改变所有权,即源指针必须赋予目标指针所有权。

C++基础之智能指针auto_ptr/unique_ptr/shared_ptr/weak_ptr

// C++ program to illustrate the use of auto_ptr
#include<iostream>
#include<memory>
using namespace std;
 
class A
{
public:
    void show() {  cout << "A::show()" << endl; }
};
 
int main()
{
    // p1 is an auto_ptr of type A
    auto_ptr<A> p1(new A);
    p1 -> show();
 
    // returns the memory address of p1
    cout << p1.get() << endl;
 
    // copy constructor called, this makes p1 empty.
    auto_ptr <A> p2(p1);
    p2 -> show();
 
    // p1 is empty now
    cout << p1.get() << endl;
 
    // p1 gets copied in p2
    cout<< p2.get() << endl;
 
    return 0;
}
A :: show()
0x1b42c20 
A :: show()
0           
0x1b42c20

复制构造函数和auto_ptr的赋值运算符实际上并不复制存储的指针而是传输它,使第一个auto_ptr对象保持为空。这是实现严格所有权的一种方法,因此在任何给定时间只有一个auto_ptr对象可以拥有指针,即不需要在需要复制语义的地方使用auto_ptr。

为什么auto_ptr已被弃用?
它以一种没有两个指针应该包含相同对象的方式获取指针的所有权。赋值转移所有权并将rvalue自动指针重置为空指针。因此,由于上述无法复制,它们不能在STL容器内使用。

 

unique_ptr

std :: unique_ptr是在C ++ 11中开发的,可替代std :: auto_ptr。
unique_ptr是一个具有类似功能的新工具,但具有更高的安全性(无伪造复制分配),添加的功能(删除器)和对阵列的支持。它是原始指针的容器。它明确地防止复制其包含的指针,就像正常赋值一样,即它只允许底层指针的一个所有者。
因此,当使用unique_ptr时,任何一个资源上最多只能有一个unique_ptr,并且当该unique_ptr被销毁时,将自动声明该资源。此外,由于任何资源只能有一个unique_ptr,因此任何尝试复制unique_ptr都会导致编译时错误。

unique_ptr <A> ptr1(新A); 

 //错误:无法复制unique_ptr 
 unique_ptr <A> ptr2 = ptr1;    

但是,unique_ptr可以使用新的移动语义移动,即使用std :: move()函数将包含指针的所有权转移到另一个unique_ptr。

// Works,资源现在存储在ptr2 
unique_ptr <A> ptr2 = move(ptr1);

因此,当我们想要一个指向一个对象的单个指针时,最好使用unique_ptr,该对象将在单个指针被销毁时被回收。

// C++ program to illustrate the use of unique_ptr
#include<iostream>
#include<memory>
using namespace std;
 
class A
{
public:
    void show()
    {
        cout<<"A::show()"<<endl;
    }
};
 
int main()
{
    unique_ptr<A> p1 (new A);
    p1 -> show();
 
    // returns the memory address of p1
    cout << p1.get() << endl;
 
    // transfers ownership to p2
    unique_ptr<A> p2 = move(p1);
    p2 -> show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
 
    // transfers ownership to p3
    unique_ptr<A> p3 = move (p2);
    p3->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
    cout << p3.get() << endl;
 
    return 0;
}
A :: show()
0x1c4ac20 
A :: show()
0 // NULL 
0x1c4ac20 
A :: show()
0 // NULL 
0 // NULL 
0x1c4ac20

下面的代码返回一个资源,如果我们没有显式捕获返回值,那么资源将被清除。如果我们这样做,那么我们拥有该资源的独家所有权。通过这种方式,我们可以将unique_ptr视为更安全,更好地替代auto_ptr。

unique_ptr <A> fun()
{ 
    unique_ptr <A> ptr(new A); 

    / * ...... 
       * / 

    return ptr; 
}

何时使用unique_ptr?
如果要拥有单一所有权(独占)资源,请使用unique_ptr。只有一个unique_ptr可以指向一个资源。由于单个资源可以有一个unique_ptr,因此无法将一个unique_ptr复制到另一个。


shared_ptr

shared_ptr是原始指针的容器。它是一个引用计数所有权模型,即它与shared_ptr的所有副本一起维护其包含指针的引用计数。因此,每当新指针指向资源时计数器就会递增,而在调用对象的析构函数时递减计数器。

引用计数:这是一种将引用,指针或句柄的数量存储到诸如对象,内存块,磁盘空间或其他资源之类的资源的技术。

包含的原始指针引用的对象在引用计数大于零之前不会被销毁,即直到删除了shared_ptr的所有副本。
因此,当我们想要将一个原始指针分配给多个所有者时,我们应该使用shared_ptr。

// C++ program to demonstrate shared_ptr
#include<iostream>
#include<memory>
using namespace std;
 
class A
{
public:
    void show()
    {
        cout<<"A::show()"<<endl;
    }
};
 
int main()
{
    shared_ptr<A> p1 (new A);
    cout << p1.get() << endl;
    p1->show();
    shared_ptr<A> p2 (p1);
    p2->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
 
    // Returns the number of shared_ptr objects
    //referring to the same managed object.
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
 
    // Relinquishes ownership of p1 on the object
    //and pointer becomes NULL
    p1.reset();
    cout << p1.get() << endl;
    cout << p2.use_count() << endl;
    cout << p2.get() << endl;
 
    return 0;
}
0x1c41c20 
A :: show()
A :: show()
0x1c41c20 
0x1c41c20 
2 
2 
0 // NULL 
1 
0x1c41c20

何时使用shared_ptr?
如果要共享资源的所有权,请使用shared_ptr。许多shared_ptr可以指向单个资源。shared_ptr维护此提议的引用计数。当指向资源的所有shared_ptr都超出范围时,资源将被销毁。


weak_ptr

weak_ptr被创建为shared_ptr的副本。它提供对一个或多个shared_ptr实例拥有的对象的访问,但不参与引用计数。weak_ptr的存在或破坏对shared_ptr或其他副本没有影响。在某些情况下,需要破坏shared_ptr实例之间的循环引用。


循环依赖(shared_ptr的问题):
让我们考虑一个我们有两个类A和B的场景,它们都有指向其他类的指针。所以,它总是像A指向B而B指向A.因此,use_count将永远不会达到零并且它们永远不会被删除。

C++基础之智能指针auto_ptr/unique_ptr/shared_ptr/weak_ptr

这就是我们使用弱指针(weak_ptr)的原因,因为它们不是引用计数的。因此,声明weak_ptr的类不具有强大的保持力,即所有权不是共享的,但是它们可以访问这些对象。

C++基础之智能指针auto_ptr/unique_ptr/shared_ptr/weak_ptr

因此,在shared_ptr的情况下,由于循环依赖,use_count永远不会达到零,这是使用weak_ptr阻止的,这通过将A_ptr声明为weak_ptr来消除此问题,因此A类不拥有它,只能访问它,我们还需要检查对象的有效性,因为它可能超出范围。一般来说,这是一个设计问题。

什么时候使用weak_ptr?
当你想从多个地方引用你的对象时 - 对于那些可以忽略和解除分配的引用(所以当你尝试取消引用时它们只会注意到对象已经消失)。


#include <iostream>
#include <memory>
#include <string>

int main(void)
{
    std::shared_ptr<std::string> ptr = std::make_shared<std::string>("hello");
    std::cout << "count : " << ptr.use_count() << std::endl;
    {
        std::shared_ptr<std::string> p = ptr;
        std::cout << "count : " << ptr.use_count() << std::endl;
        {
            std::weak_ptr<std::string> p = ptr;
            std::cout << "count : " << ptr.use_count() << std::endl;
        }
        std::cout << "count : " << ptr.use_count() << std::endl;
    }
    std::cout << "count : " << ptr.use_count() << std::endl;

    return 0;
}
/*
count : 1
count : 2
count : 2
count : 2
count : 1
*/