1. 程式人生 > >淺談智慧指標auto_ptr/shared_ptr/unique_ptr

淺談智慧指標auto_ptr/shared_ptr/unique_ptr

一.智慧指標

1.引入

我們通常使用類似new申請一塊空間,交由一個指標指向,假如說最後忘記delete,將會造成記憶體洩露。而智慧指標的出現,就是對這種問題的解決方式,智慧指標類似指標,卻可以用於管理動態分配的記憶體。本章所解說的是三種智慧指標:

(1)C++98提出,C++11摒棄的auto_ptr C++11新增的 (2)shared_ptr (3)unique_ptr 在C++裡面,三者都被以模板的形式實現,下面是對智慧指標的使用

提示: (1)智慧指標的標頭檔案是#include<memory> (2)由於unique_ptr和shared_ptr是C++11新增的智慧指標,所以編譯時需要新增支援C++11的引數 如g++ xx.cpp -o xx -std=c++11

(3)智慧指標在std域裡,需要新增名稱空間using namespace std,或者在智慧指標前加std::

//test1
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Report
{
public:
    Report(const std::string s):str(s)
    {
        cout<<"Object created"<<endl;
    }
    ~Report()
    {
        cout<<"Object deleted"<<endl;
    }
    void comment()const
    {
        cout<<str<<endl;
    }
private:
    std::string str;
};

int main(int argc, char* const argv[])
{
    //test1: use smart ptr
    {
        std::auto_ptr<Report> ps(new Report("using auto_ptr"));
        ps->comment();
    }
    {
        std::shared_ptr<Report> ps(new Report("using shared_ptr"));
        ps->comment();
    }
    {
        std::unique_ptr<Report> ps(new Report("using unique_ptr"));
        ps->comment();
    }
    return 0;
}

執行結果

[[email protected] auto_ptr]# ./t 
Object created
using auto_ptr
Object deleted
Object created
using shared_ptr
Object deleted
Object created
using unique_ptr
Object deleted

在其作用域內,分別呼叫了構造以及解構函式

2.淺談智慧指標的原理

智慧指標模板定義了類似指標的物件,將new得到的空間的地址賦給該物件,這樣,當物件過期時,其解構函式將其釋放。

3.智慧指標應該避免刪除非堆記憶體

string str("hello");
shared_ptr<string> pstr(&str);//NO!
pstr過期時,將delete非堆記憶體,錯誤
  • 1
  • 2
  • 3

4.智慧指標和常規指標

智慧指標的許多方面都類似指標,

(1)你可以對它解除引用操作(*ps) (2)可以用它訪問結構體成員(ps->puffindex) (3)將它賦給指向相同型別的常規指標 (4)將智慧指標物件賦給相同型別的智慧指標(不過會牽扯出“三”中賦值的安全性問題) 但是,智慧指標是物件,物件在過期時,會呼叫其解構函式析構掉,而常規指標,當其指向堆記憶體,最終需要人為的delete

注意:將常規指標賦給智慧指標需要進行顯示的型別轉換,如下所示

    shared_ptr<int> pd;
    int *p = new int;
    pd = shared_ptr<int>(p);
  • 1
  • 2
  • 3

二.3種智慧指標的實現策略/實現技術

兩個指標指向同一個物件時,程式可能出現刪除該物件兩次的情況,使得程式core dumpd,也就是我們常說的段錯誤。之前講C++賦值語句時,我們說到了深拷貝,這樣兩個指標指向不同的物件,其中的一個物件是另一個物件的副本。這是一種解決辦法,而auto_ptr/shared_ptr/unique_ptr使用的策略有所不同。

(1)auto_ptr和unique_ptrc採用的是ownership(建立所有權)概念,對於特定物件,只能被一個智慧指標所擁有,這樣,只有擁有該物件的智慧指標的解構函式才會刪除該物件,然後,要注意的是,賦值操作會轉讓操作權。 雖然auto_ptr和unique_ptr都採用該策略,但是unique_ptr的策略更嚴格。當出現上述情況時,程式會編譯出錯,而auto_ptr則會在執行階段core dumped。 (2)shared_ptr則採用reference counting(引用計數)的策略,例如,賦值時,計數+1,指標過期時,計數-1.只有當計數為0時,即最後一個指標過期時,才會被析構掉.

每種策略都有其用途。下面通過程式碼來分析其優劣:

三.智慧指標的優劣分析

1.unique_ptr比auto_ptr優秀

1.1優點1:unique_ptr比auto_ptr更安全

(1)auto_ptr會出現的情況

//test2
auto_ptr<Report> p1[5] =
    {
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")) 
    };
    auto_ptr<Report> p2 = p1[2];//#1
    for(int i=0; i<5; ++i)
    {
        p1[i]->comment();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

執行結果

[[email protected] auto_ptr]# ./t 
Object created
Object created
Object created
Object created
Object created
using auto_ptr 0
using auto_ptr 1
Segmentation fault (core dumped)

類似test2程式碼,(p1[2]將Report物件的所有權轉交給p2,而p1[2]此時為空指標,這樣要列印p1[2]的值時,造成了core dumped的意外)

(2)unique_ptr對該情況的處理

smrtptrs.cpp: In function ‘int main(int, char* const*)’:
smrtptrs.cpp:71:33: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Report; _Dp = std::default_delete<Report>]’
     unique_ptr<Report> p2 = p1[2];
                                 ^
In file included from /usr/include/c++/4.8.2/memory:81:0,
                 from smrtptrs.cpp:3:
/usr/include/c++/4.8.2/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^

將test2中的auto_ptr使用unique_ptr代替後,編譯器直接報上述錯誤,可見編譯器認為#1非法,避免了將p1[2]指向無效資料的情況。 這也是容器演算法禁止使用auto_ptr,但是允許使用unique_ptr的原因。 因此,unique_ptr比auto_ptr更安全 當然unique_ptr不是不允許賦值,它允許源unique_ptr是個臨時右值,如下面這個例子:

#include <iostream>
#include <string>
#include <memory>
using namespace std;

unique_ptr<string> demo1(const char* s)
{
    cout<<"DEMO1 TEST"<<endl;
    unique_ptr<string> temp(new string(s));
    return temp;
}

auto_ptr<string> demo2(const char* s)
{
    cout<<"DEMO2 TEST"<<endl;
    auto_ptr<string> temp(new string(s));
    return temp;
}
int main(int argc, char* const argv[])
{
#if __UNIQUE_PTR__
    unique_ptr<string> ps;
    ps = demo1("hello");
#endif
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所以,將一個臨時的智慧指標賦給另一個智慧指標並不會留下危險的懸掛指標

如何安全的重用unique_ptr指標

要安全的重用unique_ptr指標,可給它賦新值。C++為其提供了std::move()方法。

    unique_ptr<string> pu1(new string("nihao"));
    unique_ptr<string> pu2;
    pu2 = std::move(pu1);//move
    cout<<*pu1<<endl;//賦新值

而auto_ptr由於策略沒有unique_ptr嚴格,無需使用move方法

    auto_ptr<string> pu1, pu2;
    pu1 = demo2("Uniquely special");
    pu2 = pu1;
    pu1 = demo2(" and more");
    cout<<*pu2<<*pu1<<endl;

由於unique_ptr使用了C++11新增的移動建構函式和右值引用,所以可以區分安全和不安全的用法。

1.2優點2:unique_ptr相較auto_ptr和unique_ptr,提供了可用於陣列的變體

auto_ptr和shared_ptr可以和new一起使用,但不可以和new[]一起使用,但是unique_ptr可以和new[]一起使用

unique_ptr<double[]> pda(new double(5));

2.相較之下,shared_ptr的策略更加機智

由於shared_ptr使用的是引用計數的策略,所以賦值時,不需要考慮源智慧指標為空值的情況

    shared_ptr<string> p1(new string("hello"));
    shared_ptr<string> p2;
    p2 = p1;
    cout<<"p1"<<*p1<<endl<<"p2"<<*p2<<endl;

四.選擇智慧指標

《C++ primer plus》第六版P673有詳細講解,這裡簡單闡述一下

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <stdlib.h>
using namespace std;

unique_ptr<int> make_int(int n)
{
    return unique_ptr<int> (new int(n));
}

void show(unique_ptr<int> & pi)     //pass by reference
{
    cout<< *pi <<' ';
}

int main()
{
    vector<unique_ptr<int> > vp(5);
    for(int i = 0; i < vp.size(); ++i)
    {
        vp[i] = make_int(rand() % 1000);//copy temporary unique_ptr
    }
    vp.push_back(make_int(rand() % 1000));//ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);
    unique_ptr<int> pup(make_int(rand() % 1000));
   // shared_ptr<int> spp(pup);//not allowed. pup is lvalue
    shared_ptr<int> spr(make_int(rand() % 1000));

    return 0;
}

(1)當多個物件指向同一個物件的指標時,應選擇shared_ptr (2)用new申請的記憶體,返回指向這塊記憶體的指標時,選擇unique_ptr就不錯 (3)在滿足unique_ptr要求的條件時,前提是沒有不明確的賦值,也可以使用auto_ptr (4)如上述程式碼所示,unique_ptr為右值(不準確的說類似無法定址)時,可以賦給shared_ptr