欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++手擼智能指針的教程分享

 更新時間:2023年05月21日 16:49:57   作者:會玩code  
在前文中小編為大家介紹了C++智能指針的一些使用方法和基本原理,所以本文就來自己動手,從0到1實現一下自己的unique_ptr和shared_ptr吧

前言

大家好,今天是【重學C++】的第三講,書接上回,第二講《02 脫離指針陷阱:深入淺出 C++ 智能指針》介紹了C++智能指針的一些使用方法和基本原理。今天,我們自己動手,從0到1實現一下自己的unique_ptrshared_ptr。

回顧

智能指針的基本原理是基于RAII設計理論,自動回收內存資源,從根本上避免內存泄漏。在第一講《01 C++ 如何進行內存資源管理?》介紹RAII的時候,就已經給了一個用于封裝int類型指針,實現自動回收資源的代碼實例:

class?AutoIntPtr?{
public:
????AutoIntPtr(int*?p?=?nullptr)?:?ptr(p)?{}
????~AutoIntPtr()?{?delete?ptr;?}
????int&?operator*()?const?{?return?*ptr;?}
????int*?operator->()?const?{?return?ptr;?}
private:
????int*?ptr;
};

我們從這個示例出發(fā),一步步完善我們自己的智能指針。

模版化

這個類有個明顯的問題:只能適用于int類指針。所以我們第一步要做的,就是把它改造成一個類模版,讓這個類適用于任何類型的指針資源。code show time

template?<typename?T>
class?smart_ptr?{
public:
?explicit?smart_ptr(T*?ptr?=?nullptr):?ptr_(ptr)?{}
?~smart_ptr()?{
??delete?ptr_;
?}
?T&?operator*()?const?{?return?*ptr_;?}
?T*?operator->()?const?{?return?ptr_;?}
private:
?T*?ptr_;
}

我給我們的智能指針類用了一個更抽象,更切合的類名:smart_ptr。

AutoIntPtr相比,我們把smart_ptr設計成一個類模版,原來代碼中的int改成模版參數T,非常簡單。使用時也只要把AutoIntPtr(new int(9)) 改成smart_ptr<int>(new int(9))即可。

另外,有一點值得注意,smart_ptr的構造函數使用了explicit, explicit關鍵字主要用于防止隱式的類型轉換。代碼中,如果原生指針隱式地轉換為智能指針類型可能會導致一些潛在的問題。至于會有什么問題,你那聰明的小腦瓜看完下面的代碼肯定能理解了:

void?foo(smart_ptr<int>?int_ptr)?{
????//?...
}
int?main()?{
????int*?raw_ptr?=?new?int(42);
????foo(raw_ptr);??//?隱式轉換為?smart_ptr<int>
????std::cout?<<?*raw_ptr?<<?std::endl;???//?error:?raw_ptr已經被回收了
????//?...
}

假設我們沒有為smart_ptr構造函數加上explicit,原生指針raw_ptr在傳給foo函數后,會被隱形轉換為smart_ptr<int>, foo函數調用結束后,棲構入參的smart_ptr<int>時會把raw_ptr給回收掉了,所以后續(xù)對raw_ptr的調用都會失敗。

拷貝還是移動

當前我們沒有為smart_ptr自定義拷貝構造函數/移動構造函數,C++會為smart_ptr生成默認的拷貝/移動構造函數。默認的拷貝/移動構造函數邏輯很簡單:把每個成員變量拷貝/移動到目標對象中。

按當前smart_ptr的實現,我們假設有以下代碼:

smart_ptr<int>?ptr1{new?int(10)};
smart_ptr<int>?ptr2?=?ptr1;

這段代碼在編譯時不會出錯,問題在運行時才會暴露出來:第二行將ptr1管理的指針復制給了ptr2,所以會重復釋放內存,導致程序奔潰。

為了避免同一塊內存被重復釋放。解決辦法也很簡單:

  • 獨占資源所有權,每時每刻一個內存對象(資源)只能有一個smart_ptr占有它。
  • 一個內存對象(資源)只有在最后一個擁有它的smart_ptr析構時才會進行資源回收。

獨占所有權 - unique_smart_ptr

獨占資源的所有權,并不是指禁用掉smart_ptr的拷貝/移動函數(當然這也是一種簡單的避免重復釋放內存的方法)。而是smart_ptr在拷貝時,代表資源對象的指針不是復制到另外一個smart_ptr,而是"移動"到新smart_ptr。移動后,原來的smart_ptr.ptr_ == nullptr, 這樣就完成了資源所有權的轉移。這也是C++ unique_ptr的基本行為。我們在這里先把它命名為unique_smart_ptr,代碼完整實現如下:

template?<typename?T>
class?unique_smart_ptr?{
public:
?explicit?unique_smart_ptr(T*?ptr?=?nullptr):?ptr_(ptr)?{}
?~unique_smart_ptr()?{
??delete?ptr_;
?}
?//?1.?自定義移動構造函數
?unique_smart_ptr(unique_smart_ptr&&?other)?{
??//?1.1?把other.ptr_?賦值到this->ptr_
??ptr_?=?other.ptr_;
??//?1.2?把other.ptr_指為nullptr,other不再擁有資源指針
??other.ptr_?=?nullptr;
?}
?//?2.?自定義賦值行為
?unique_smart_ptr&?operator?=?(unique_smart_ptr?rhs)?{
??//?2.1?交換rhs.ptr_和this->ptr_
??std::swap(rhs.ptr_,?this->ptr_);
??return?*this;
?}
T&?operator*()?const?{?return?*ptr_;?}
T*?operator->()?const?{?return?ptr_;?}
private:
?T*?ptr_;
};

自定義移動構造函數。在移動構造函數中,我們先是接管了other.ptr_指向的資源對象,然后把otherptr_置為nullptr,這樣在other析構時就不會錯誤釋放資源內存。

同時,根據C++的規(guī)則,手動提供移動構造函數后,就會自動禁用拷貝構造函數。也就是我們能得到以下效果:

unique_smart_ptr<int>?ptr1{new?int(10)};
unique_smart_ptr<int>?ptr2?=?ptr1;?//?error
unique_smart_ptr<int>?ptr3?=?std::move(ptr1);?//?ok
unique_smart_ptr<int>?ptr4{ptr1}?//?error
unique_smart_ptr<int>?ptr5{std::move(ptr1)}?//?ok

自定義賦值函數。在賦值函數中,我們使用std::swap交換了 rhs.ptr_this->ptr_,注意,這里不能簡單的將rhs.ptr_設置為nullptr,因為this->ptr_可能有指向一個堆對象,該對象需要轉給rhs,在賦值函數調用結束,rhs析構時順便釋放掉。避免內存泄漏。

注意賦值函數的入參rhs的類型是unique_smart_ptr而不是unique_smart_ptr&&,這樣創(chuàng)建rhs使用移動構造函數還是拷貝構造函數完全取決于unique_smart_ptr的定義。因為unique_smart_ptr當前只保留了移動構造函數,所以rhs是通過移動構造函數創(chuàng)建的。

多個智能指針共享對象 - shared_smart_ptr

學過第二講的shared_ptr, 我們知道它是利用計數引用的方式,實現了多個智能指針共享同一個對象。當最后一個持有對象的智能指針析構時,計數器減為0,這個時候才會回收資源對象。

我們先給出shared_smart_ptr的類定義

template?<typename?T>
class?shared_smart_ptr?{
public:
?//?構造函數
?explicit?shared_smart_ptr(T*?ptr?=?nullptr)
?//?析構函數
?~shared_smart_ptr()
?//?移動構造函數
?shared_smart_ptr(shared_smart_ptr&&?other)
?//?拷貝構造函數
?shared_smart_ptr(const?shared_smart_ptr&?other)
?//?賦值函數
?shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs)
?//?返回當前引用次數
?int?use_count()?const?{?return?*count_;?}
?T&?operator*()?const?{?return?*ptr_;?}
?T*?operator->()?const?{?return?ptr_;?}
private:
?T*?ptr_;
?int*?count_;
}

暫時不考慮多線程并發(fā)安全的問題,我們簡單在堆上創(chuàng)建一個int類型的計數器count_。下面詳細展開各個函數的實現。

為了避免對count_的重復刪除,我們保持:只有當ptr_ != nullptr時,才對count_進行賦值。

構造函數

同樣的,使用explicit避免隱式轉換。除了賦值ptr_, 還需要在堆上創(chuàng)建一個計數器。

explicit?shared_smart_ptr(T*?ptr?=?nullptr){
?ptr_?=?ptr;
?if?(ptr_)?{
??count_?=?new?int(1);
?}
}

析構函數

在析構函數中,需要根據計數器的引用數判斷是否需要回收對象。

~shared_smart_ptr()?{
?//?ptr_為nullptr,不需要做任何處理
?if?(ptr_)?{
??return;
?}
?//?計數器減一
?--(*count_);
?//?計數器減為0,回收對象
?if?(*count_?==?0)?{
??delete?ptr_;
??delete?count_;
??return;
?}
}

移動構造函數

添加對count_的處理

shared_smart_ptr(shared_smart_ptr&&?other)?{
?ptr_?=?other.ptr_;
?count_?=?other.count_;
?other.ptr_?=?nullptr;
?other.count_?=?nullptr;
}

賦值構造函數

添加交換count_

shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs)?{
?std::swap(rhs.ptr_,?this->ptr_);
?std::swap(rhs.count_,?this->count_);
?return?*this;
}

拷貝構造函數

對于shared_smart_ptr,我們需要手動支持拷貝構造函數。主要處理邏輯是賦值ptr_和增加計數器的引用數。

shared_smart_ptr(const?shared_smart_ptr&?other)?{
?ptr_?=?other.ptr_;
?count_?=?other.count_;
?if?(ptr_)?{
??(*count_)++;
?}
}

這樣,我們就實現了一個自己的共享智能指針,貼一下完整代碼

template?<typename?T>
class?shared_smart_ptr?{
public:
?explicit?shared_smart_ptr(T*?ptr?=?nullptr){
??ptr_?=?ptr;
??if?(ptr_)?{
???count_?=?new?int(1);
??}
?}
?~shared_smart_ptr()?{
??//?ptr_為nullptr,不需要做任何處理
??if?(ptr_?==?nullptr)?{
???return;
??}
??//?計數器減一
??--(*count_);
??//?計數器減為0,回收對象
??if?(*count_?==?0)?{
???delete?ptr_;
???delete?count_;
??}
?}
?shared_smart_ptr(shared_smart_ptr&&?other)?{
??ptr_?=?other.ptr_;
??count_?=?other.count_;
??other.ptr_?=?nullptr;
??other.count_?=?nullptr;
?}
?shared_smart_ptr(const?shared_smart_ptr&?other)?{
??ptr_?=?other.ptr_;
??count_?=?other.count_;
??if?(ptr_)?{
???(*count_)++;
??}
?}
?shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs)?{
??std::swap(rhs.ptr_,?this->ptr_);
??std::swap(rhs.count_,?this->count_);
??return?*this;
?}
?int?use_count()?const?{?return?*count_;?};
?T&?operator*()?const?{?return?*ptr_;?};
?T*?operator->()?const?{?return?ptr_;?};
private:
?T*?ptr_;
?int*?count_;
};

使用下面代碼進行驗證:

int?main(int?argc,?const?char**?argv)?{
?shared_smart_ptr<int>?ptr1(new?int(1));
?std::cout?<<?"[初始化ptr1]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl;
?{
??//?賦值使用拷貝構造函數
??shared_smart_ptr<int>?ptr2?=?ptr1;
??std::cout?<<?"[使用拷貝構造函數將ptr1賦值給ptr2]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl;
??//?賦值使用移動構造函數
??shared_smart_ptr<int>?ptr3?=?std::move(ptr2);
??std::cout?<<?"[使用移動構造函數將ptr2賦值給ptr3]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl;
?}
?std::cout?<<?"[ptr2和ptr3析構后]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl;
}

運行結果:

[初始化ptr1] use count of ptr1: 1
[使用拷貝構造函數將ptr1賦值給ptr2] use count of ptr1: 2
[使用移動構造函數將ptr2賦值給ptr3] use count of ptr1: 2
[ptr2和ptr3析構后] use count of ptr1: 1

總結

這一講我們從AutoIntPtr出發(fā),先是將類進行模版化,使其能夠管理任何類型的指針對象,并給該類起了一個更抽象、更貼切的名稱——smart_ptr

接著圍繞著「如何正確釋放資源對象指針」的問題,一步步手擼了兩個智能指針 ——unique_smart_ptrshared_smart_ptr。相信大家現在對智能指針有一個較為深入的理解了。

以上就是C++手擼智能指針的教程分享的詳細內容,更多關于C++智能指針的資料請關注腳本之家其它相關文章!

相關文章

  • 詳解Qt如何加載libxl庫

    詳解Qt如何加載libxl庫

    這篇文章主要介紹了詳解Qt如何加載libxl庫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • 詳解C++中的內存同步模式(memory order)

    詳解C++中的內存同步模式(memory order)

    這篇文章主要介紹了C++中的內存同步模式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • 全面了解#pragma once與 #ifndef的區(qū)別

    全面了解#pragma once與 #ifndef的區(qū)別

    下面小編就為大家?guī)硪黄媪私?pragma once與 #ifndef的區(qū)別。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • 一些語言的按行讀取文件的代碼實現小結

    一些語言的按行讀取文件的代碼實現小結

    這篇文章主要介紹了一些語言的按行讀取文件的代碼實現小結,這里羅列了Java和C語言和C++以及PHP的實現需要的朋友可以參考下
    2015-08-08
  • C++中關鍵字const的詳細說明和使用介紹(最全)

    C++中關鍵字const的詳細說明和使用介紹(最全)

    const在C/C++中是十分重要的,如果單純理解為“常量”那么你的格局就小了,今天在這里給大家介紹一下const在C++中具體詳細的用法,需要的朋友可以參考下
    2025-03-03
  • C++中cin的返回值問題

    C++中cin的返回值問題

    這篇文章主要介紹了C++中cin的返回值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • C++ 中CListCtrl的每個項都顯示不同的提示信息

    C++ 中CListCtrl的每個項都顯示不同的提示信息

    這篇文章主要介紹了C++ 中CListCtrl的每個項都顯示不同的提示信息的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Matlab實現二維散點主方向直方圖的繪制詳解

    Matlab實現二維散點主方向直方圖的繪制詳解

    這篇文章主要為大家詳細介紹了如何利用Matlab實現二維散點主方向直方圖的繪制,文中的示例代碼講解詳細,對我們學習Matlab有一定幫助,需要的可以參考一下
    2022-09-09
  • C++/C 回文字符串的實例詳解

    C++/C 回文字符串的實例詳解

    這篇文章主要介紹了C++ 回文字符串的實例詳解的相關資料,需要的朋友可以參考下
    2017-07-07
  • 使用C語言判斷棧的方向實例

    使用C語言判斷棧的方向實例

    下面小編就為大家?guī)硪黄褂肅語言判斷棧的方向實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,祝大家游戲愉快哦
    2016-12-12

最新評論