c++11特性(六)智能指针

智能指针

  • 独占智能指针
    • 使用方法
    • unique_ptr的reset方法
    • 获取原始地址
  • 共享智能指针
    • 使用方法
  • 弱引用智能指针
    • 使用方法
    • 常用api函数
    • 循环引用

在c++98中,我们new一个对象出来后,需要手动delete,否则会造成内存泄漏,但是难免会有忘记的时候,所以在c++11中就引入了新特性智能指针(smart pointer)。

智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存

c++11提供了四种智能指针

  • std::auto_ptr:拷贝、赋值构造时会发生管理权转移问题,不再使用
  • std::unique_ptr:独占智能指针
  • st::shared_ptr:共享智能指针
  • std::weak_ptr:若引用指针,不共享智能指针,也不操作资源,只是用来监视共享智能指针的

独占智能指针

独占智能指针就是std::unique_ptr指针,它不允许其他智能指针共享其内部的指针,可以通过构造函数构造一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另外一个unique_ptr指针,内部是对赋值构造函数给为delete的

// 通过构造函数初始化对象
std::unique_ptr<int> ptr1(new int(10));
// error, 不允许将一个unique_ptr赋值给另一个unique_ptr
std::unique_ptr<int> ptr2 = ptr1;

使用方法

1、将对象new出来

#include     
#include     
    
class Test{    
public:    
    Test(int num)    
        :_num(num){}    
    void test(){    
        std::cout << _num << std::endl;    
    }    
private:    
    int _num;    
};    
    
int main(){    
    std::unique_ptr<Test> ptr(new Test(10));    
    ptr->test();    
    return 0;                                                                                                                     
}    

输出结果如下:

10

2、赋值方式,将该new出来的智能指针赋值给另外一个智能指针

#include   
#include   
    
class Test{  
public:  
    Test(int num)  
        :_num(num){}  
    void test(){  
        std::cout << _num << std::endl;  
    }  
private:  
    int _num;  
};  
    
int main(){  
    std::unique_ptr<Test> ptr(new Test(10));
    std::unique_ptr<Test> ptr1;
    ptr1 = ptr;                                                                                                                 
    ptr->test();
    return 0;
}       

输出结果如下:

/root/test/main.cpp: 在函数‘int main()’中:
/root/test/main.cpp:18:10: 错误:使用了被删除的函数‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Test; _Dp = std::default_delete<Test>]’
     ptr1 = ptr;
          ^
In file included from /usr/include/c++/4.8.2/memory:81:0,
                 from /root/test/main.cpp:2:
/usr/include/c++/4.8.2/bits/unique_ptr.h:274:19: 错误:在此声明
       unique_ptr& operator=(const unique_ptr&) = delete;

从输出结果可以看出赋值构造函数被delete了,所以这就是std::unique_ptr不能被赋值的原因,因为它智能独占资源,别人不能和他共享

3、通过函数返回给其他的unique_ptr

#include       
#include       
      
class Test{      
public:      
    Test(int num)      
        :_num(num){}      
    void test(){      
        std::cout << _num << std::endl;      
    }      
private:      
    int _num;      
};      
      
std::unique_ptr<Test> func(){      
    return std::unique_ptr<Test>(new Test(10));      
}      
      
int main(){      
    std::unique_ptr<Test> ptr = func();    
    ptr->test();                                                                                                                  
    return 0;                                   
}            

输出结果如下:

10

4、通过std::move转移给其他智能指针

#include       
#include       
      
class Test{      
public:      
    Test(int num)      
        :_num(num){}      
    void test(){      
        std::cout << _num << std::endl;      
    }      
private:      
    int _num;      
};      
      
  
int main(){  
    std::unique_ptr<Test> ptr(new Test(10));
    std::unique_ptr<Test> ptr1;
    ptr1 = std::move(ptr);
    ptr1->test();                                                                                                                 
    return 0;                                                     
}        

输出结果如下:

10

使用c++11中的std::move就是发生管理权转移,将智能指针ptr的管理权转移给智能指针ptr1

注意:将智能指针ptr管理权转移给智能指针ptr1后,原始智能指针ptr就不可再使用,否则会发生段错误,因为ptr已经不再拥有该资源了,如果再次使用ptr,会找不到该资源的地址,导致发生段错误

unique_ptr的reset方法

函数原型如下:

void reset( pointer ptr = pointer() ) noexcept;

使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针

int main()
{
    unique_ptr<int> ptr1(new int(10));
    unique_ptr<int> ptr2 = move(ptr1);

    ptr1.reset();
    ptr2.reset(new int(250));
    return 0;
}
  • ptr1:解除对原始内存的管理
  • ptr2:重新指定原始内存的地址

获取原始地址

函数原型如下:

pointer get() const noexcept;
#include     
#include     
    
int main(){    
    std::unique_ptr<int> ptr1(new int(10));    
    std::unique_ptr<int> ptr2;    
    ptr2 = std::move(ptr1);    
    ptr2.reset(new int(666));   
                                                                                                      
    //  得到的结果是666                                               
    std::cout << *ptr2.get() << std::endl;           
    return 0;                                        
}     

共享智能指针

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类,如果要进行初始化有三种方式:

  • 构造函数
  • std::make_shared
  • reset

共享智能指针初始化完了就指向管理的那段内存,如果想要获取当前有多少个共享智能指针同时指向这块内存,可以使用成员函数use_count
函数原型如下:

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;

使用方法

1、使用构造函数初始化智能指针

#include     
#include     
    
int main(){  
    std::shared_ptr<int> ptr(new int(100));  
    std::cout << "ptr管理内存引用计数: " << ptr.use_count() << std::endl;

    std::shared_ptr<int> ptr1;
    std::cout << "ptr1管理内存引用计数: " << ptr1.use_count() << std::endl;

    std::shared_ptr<int> ptr2(nullptr);
    std::cout << "ptr2管理内存引用计数: " << ptr2.use_count() << std::endl;                                                       
    return 0;                                    
}        

输出结果如下:

ptr管理内存引用计数: 1
ptr1管理内存引用计数: 0
ptr2管理内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 shared_ptr。

例如:

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);		// error, 编译不会报错, 运行会出错

2、通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了

#include       
#include       
      
int main(){      
    std::shared_ptr<int> ptr(new int(100));      
    std::cout << "ptr管理内存引用计数: " << ptr.use_count() << std::endl;      
      
    std::shared_ptr<int> ptr1(ptr);      
    std::cout << "ptr1管理内存引用计数: " << ptr1.use_count() << std::endl;      
      
    std::shared_ptr<int> ptr2 = ptr;      
    std::cout << "ptr2管理内存引用计数: " << ptr2.use_count() << std::endl;      
      
    std::shared_ptr<int> ptr3(std::move(ptr1));      
    std::cout << "ptr3管理内存引用计数: " << ptr3.use_count() << std::endl;      
      
    std::shared_ptr<int> ptr4 = std::move(ptr2);    
    std::cout << "ptr4管理内存引用计数: " << ptr4.use_count() << std::endl;                                                       
    return 0;                                                                
}      

输出结果如下:

ptr管理内存引用计数: 1
ptr1管理内存引用计数: 2
ptr2管理内存引用计数: 3
ptr3管理内存引用计数: 3
ptr4管理内存引用计数: 3

如果采用拷贝的方式进行shared_ptr的初始化,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加,如果使用移动构造的方式初始智能指针对象,只是转让了内存的管理权,管理内存的对象并不会增加,因此内存的引用计数不会变化

3、通过std::make_shared进行初始化
通过 C++ 提供的 std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

具体使用示例

#include     
#include     
    
int main(){                                                                                                                       
    std::shared_ptr<int> ptr = std::make_shared<int>(100);    
    return 0;    
}   

使用 std::make_shared() 模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数

弱引用智能指针

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和 ->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在

使用方法

#include     
#include     
    
int main(){  
    std::shared_ptr<int> ptr;  
    std::weak_ptr<int> ptr1;
    std::weak_ptr<int> ptr2(ptr1);
    std::weak_ptr<int> ptr3 = ptr1;
    std::weak_ptr<int> ptr4(ptr);                                                                                                 
    return 0;              
}      

常用api函数

1、use_count()
通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数,函数原型如下:

// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;
#include     
#include     
    
int main(){    
    std::shared_ptr<int> ptr(new int(100));    
    std::weak_ptr<int> ptr1 = ptr;                                                                                                
    std::cout << ptr1.use_count() << std::endl;                    
                                                                   
    return 0;                                                      
}   

输出结果如下:

1

通过上述结果可知,weak_ptr不对引用计数有任何改变

2、expired()
通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放,函数原型如下:

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;
#include 
#include 
using namespace std;

int main() 
{
    shared_ptr<int> shared(new int(10));
    weak_ptr<int> weak(shared);
    cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    shared.reset();
    cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

输出结果如下:

1. weak is not expired
2. weak is expired

weak_ptr 监测的就是 shared_ptr 管理的资源,当共享智能指针调用 shared.reset(); 之后管理的资源被释放,因此 weak.expired() 函数的结果返回 true,表示监测的资源已经不存在了

循环引用

对于shared_ptr的使用,存在一个循环引用的问题,循环引用会导致内存泄漏

#include 
#include 
using namespace std;

struct TA;
struct TB;

struct TA
{
    shared_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

输出结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2

共享智能指针 ap、bp 对 TA、TB 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 TA、TB 的实例对象不能被析构,最终造成内存泄露。通过使用 weak_ptr 可以解决这个问题,只要将类 TA 或者 TB 的任意一个成员改为 weak_ptr,修改之后的代码如下:

#include 
#include 
using namespace std;

struct TA;
struct TB;

struct TA
{
    weak_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

输出结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...

通过输出的结果可以看到类 TA 或者 TB 的对象被成功析构了。

上面程序中,在对类 TA 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 TB 的实例对象被析构。

在类 TB 的实例对象被析构的时候,内部的 aptr 也被析构,其对 TA 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 TA 对象的管理也解除了,内存的引用计数减为 0,类 TA 的实例对象被析构。

参考:
爱编程的大丙

你可能感兴趣的:(C++语言,c++,开发语言,算法)