<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #Item 22:當使用Pimpl的時候在實現文件中定義特殊的成員函數 如果你曾經因為程序過多的build次數頭疼過,你肯定對于Pimpl(pointer to implementation)做法很熟悉。它的做法是:把對象的成員變量替換為一個指向已經實現類(或者是結構體)的指針.將曾經在主類中的數據成員轉移到該實現類中,通過指針來間接的訪問這些數據成員。舉個列子,假設Widget看起來像是這樣: ```cpp class Widget{ //in header "widget.h" public: Widget(); ... private: std::string name; std::vector<double> data: Gadget g1,g2,g3; //Gadget is some user-defined type } ``` 因為Widget的數據成員是std::string, std::vector以及Gadget類型,為了編譯Widget,這些類型的頭文件必須被包含進來,使用Widget的客戶必須#include <string>,<vector>,以及gadget.h。這些頭文件增加了使用Widget的客戶的編譯時間,并且讓客戶依賴這些頭文件的內容。如果一個頭文件里的內容發生了改變,使用Widget的客戶必須被重新編譯。雖然標準的頭文件<string>和<vector>不怎么發生變化,但gadget.h的頭文件可能經常變化。 應用C++ 98的Pimpl做法,我們將數據成員變量替換為一個原生指針,指向了一個只是聲明并沒有被定義的結構體 ```cpp class Widget{ //still in header "widget.h" public: Widget(); ~Widget(); //dtor is needed-see below ... private: struct Impl; //declare implementation struct Impl *pImpl; //and pointer to it } ``` Widget已經不再引用std::string,std::vector以及gadget類型,使用Widget的客戶就可以不#include這些頭文件了。這加快了編譯速度,并且Widget的客戶也不受到影響。 一種只聲明不定義的類型被稱作incomplete type.Widget::Impl就是incomplete type。對于incomplete type,我們能做的事情很少,但是我們可以聲明一個指向它的指針。Pimpl做法就是利用了這一點。 Pimpl做法的第一步是:聲明一個成員變量,它是一個指向incomplete type的指針。第二步是,為包含以前在原始類中的數據成員的對象(本例中的*pImpl)做動態分配內存和回收內存。分配以及回收的代碼在實現文件中。本例中,對于Widget而言,這些操作在widget.cpp中進行: ```cpp #include "widget.h" //in impl,file "widget.cpp" #include "gadget.h" #include <string> #include <vector> struct Widget::Impl{ std::string name; //definition of Widget::Impl with data members formerly in Widget std::vector<double> data; Gadget g1,g2,g3; } Widget::Widget():pImpl(new Impl) //allocate data members for this Widget object {} Widget::~Widget() //destroy data members for this object { delete pImpl; } ``` 在上面的代碼中,我還是使用了#include指令,表明對于std::string,std::vector以及Gadget頭文件的依賴還繼續存在。然后,這些依賴從widget.h(被使用Widget的客戶所使用,對它們可見)轉移到了widget.cpp(只對Widget的實現者可見)中,我已經高亮了(不好意思,在代碼中高亮這種語法俺做不到啊---譯者注)分配和回收Impl對象的代碼。因為需要在Widget析構是,對Impl對象的內存進行回收,所以Widget的析構函數是必須要寫的。 但是我給你展示的是C++ 98的代碼,散發著上個世紀的上古氣息.使用了原生指針,原生的new和原生的delete,全都是原生的啊!本章的內容是圍繞著“智能指針大法好,退原生指針保平安”的理念。如果我們想要在一個Widget的構造函數中動態分配一個Widget::Impl對象,并且在Widget析構時,也析構Widget::Impl對象。那么std::unique_ptr(請看Item 18)就是我們想要的最合適的工具。將原生pImpl指針替換為std::unique_ptr。頭文件的內容變為這樣: ```cpp class Widget{ public: Widget(); ... private: struct Impl; std::unique_ptr<Impl> pImpl;//use smart pointer //instead of raw pointer } ``` 實現文件的內容則變成了這樣: ```cpp #include "widget.h" //in "widget.cpp" #include "gadget.h" #include <string> #include <vector> struct Widget::Impl{ std::string name; //as before std::vector<double> data; Gadget g1,g2,g3; } Widget::Widget() :pImpl(std::make_unique<Impl>()) //per Item 21,create {} //std::unique_ptr //via std::make_unique ``` 你會發現Widget的析構函數不復存在。這是因為我們不需要在析構函數里面寫任何代碼了。std::unique_ptr在自身銷毀時自動析構它指向的區域,所以我們不需要自己回收任何東西。這就是智能指針的一項優點:它們消除了我們需要手動釋放資源的麻煩。 但是呢,使用Widget的客戶的一句很平凡的用法,就編譯出錯了啊 ```cpp #include "widget.h" Widget w; //error ``` 你所受到的錯誤信息內容依賴于你所使用的編譯器類型,但是產生的內容大致都是:在incomplete type上使用了sizeof和delete.這些操作在該類型上是禁止的。 Pimpl做法結合std::unique_ptr竟然會產生錯誤,這很讓人震驚啊。因為(1)std::unique_ptr自身標榜支持incomplete type.(2)Pimpl做法是std::unique_ptr眾多的使用場景之一。幸運的是,讓代碼工作起來也是很簡單地。我們首先需要理解為啥會出錯。 在執行w被析構(如當出作用域時)的代碼時,報了錯誤。在此時,Widget的析構函數被調用。在定義使用std::unique_ptr的Widget的時候,我們并沒有聲明析構函數,因為我們不需要在Widget的析構函數內寫任何代碼。依據編譯器自動生成特殊成員函數(請看Item 17)的普通規則,編譯器為我們生成了一個析構函數。在那個自動生成的析構函數中,編譯器插入代碼,調用Widget的數據成員pImpl的析構函數。pImpl是一個`std::unique_ptr<Widget::Impl>`,即,一個使用默認deleter的std::unique_ptr.默認deleter是一個函數,對std::unique_ptr里面的原生指針調用delete.然而,在調用delete之前,編譯器通常會讓默認deleter先使用C++ 11的static_assert來確保原生指針指向的類型不是imcomplete type(staticassert編譯時候檢查,assert運行時檢查---譯者注).當編譯器生成Widget w的析構函數時,調用的static_assert檢查就會失敗,導致出現了錯誤信息。在w被銷毀時,這些錯誤信息才會出現,但是因為與其他的編譯器生成的特殊成員函數相同,Widget的析構函數也是inline的。出錯指向w被創建的那一行,因為改行創建了w,導致后來(w出作用域時)w被隱性銷毀。 為了修復這個問題,你需要確保,在產生銷毀`std::unique_ptr<Widget::Impl>`的代碼時,Widget::Impl是完整的類型。當它的定義被編譯器看到時,它就是完整類型了。而Widget::Impl在widget.cpp中被定義。所以編譯成功的關鍵在于,讓編譯器只在widget.cpp內,在widget::Impl被定義之后,看到Widget的析構函數體(該函數體就是放置編譯器自動生成銷毀std::unique_ptr數據成員的代碼的地方)。 像那樣安排很簡單,在widget.h中聲明Widget的析構函數,但是不要在其中定義: ```cpp class Widget{ //as before, in "widget.h" public: Widget(); ~Widget(); //declaration only ... private: //as before struct Impl; std::unique_ptr<Impl> pImpl; } ``` 在widget.cpp里面的Widget::Impl定義之后再定義析構函數: ```cpp #include "widget.h" //as before in "widget.cpp" #include "gadget.h" #include <string> #include <vector> struct Widget::Impl{ std::string name; //as before definition of std::vector<double> data; //Widget::Impl Gadget g1,g2,g3; } Widget::Widget() :pImpl(std::make_unique<Impl>()) //as before Widget::~Widget(){} //~Widget definition ``` 這樣的話就沒問題了,增加的代碼量也很少。但是如果你想要強調,編譯器生成的析構函數會做正確的事情,你聲明析構函數的唯一原因是,想要在Widget.cpp中生成它的定義,你可以用“=default”定義析構函數體: ```cpp Widget::~Widget()=default; //same effect as above ``` 使用Pimpl做法的類是自帶支持move語義的候選者,因為編譯器生成的move操作正是我們想要的:對潛在的std::unique_ptr上執行move操作。就像Item 17解釋的那樣,Widget聲明了析構函數,編譯器就不會自動生成move操作了。所以如果你想要支持move,你必須自己去聲明這些函數。鑒于編譯器生成的版本就可以勝任,你可能會像下面那樣實現: ```cpp class Widget{ //still in "widget.h" public: Widget(); ~Widget(); ... Widget(Widget&& rhs) = default; //right idea, Widget& operator=(Widget&& rhs) = default //wrong code! ... private: //as before struct Impl; std::unique_ptr<Impl> pImpl; }; ``` 這樣的做法會產生和聲明一個沒有析構函數的class一樣,產生同樣的問題,產生問題的原因本質也一樣。對于編譯器生成的move賦值操作符,它對pImpl再賦值之前,需要先銷毀它所指向的對象,然而在Widget頭文件中,pImpl指向的仍是一個incomplete type.對于編譯器生成的move構造函數。問題在于編譯器會在move構造函數內拋出異常的事件中,生成析構pImpl的代碼,對pImpl析構(destroying pImpl)需要Impl的類型是完整的。 問題一樣,解決方法自然也一樣:將move操作的定義寫在實現文件widget.cpp中: widget.h: ```cpp class Widget{ //still in "widget.h" public: Widget(); ~Widget(); ... Widget(Widget&& rhs); //declarations Widget& operator=(Widget&& rhs); //only ... private: //as before struct Impl; std::unique_ptr<Impl> pImpl; }; ``` widget.cpp ```cpp #include <string> //as before, ... //in "widget.cpp" sturct:Widget::Impl {...}; //as before Widget::Widget() //as before :pImpl(std::make_unique<Impl>()) {} Widget::~Widget() = default; //as before Widget::Widget(Widget&& rhs) = default; Widget& Widget::operator=(Widget&& rhs) = default: //definitions ``` Pimpl做法是一種減少class的實現和class的使用之間編譯依賴的一種方式,但是,從概念上來講,這種做法并不改變類的表現方式。原來的Widget類包含了std::string,std::vector以及Gadget數據成員,并且,假設Gadget,像std::string和std::vector那樣,可以被拷貝。所以按理說Widget也要支持拷貝操作。我們必須要自己手寫這些拷貝函數了,因為(1)對于帶有move-only類型(像std::unique_ptr)的類,編譯器不會生成拷貝操作的代碼.(2)即使生成了,生成的代碼只會拷貝std::unique_ptr(即,執行淺拷貝),而我們想要的是拷貝指針所指向的資源(即,執行深拷貝)。 現在的做法我們已經熟悉了,在頭文件中聲明這些函數,然后在實現文件中實現這些函數; widget.h: ```cpp class Widget{ //still in "widget.h" public: ... //other funcs, as before Widget(const Widget& rhs); //declarations Widget& operator=(const Widget& rhs); //only private: //as before struct Impl; std::unique_ptr<Impl> pImpl; }; ``` widget.cpp ```cpp #include <string> //as before, ... //in "widget.cpp" sturct:Widget::Impl {...}; //as before Widget::~Widget() = default; //other funcs,as before Widget::Widget(const Widget& rhs); //copy ctor : pImpl(std::make_unique<Impl>(*rhs.pImpl)) {} Widget& Widget::operator=(const Widget& rhs)//copy operator= { *pImpl = *rhs.pImpl; return *this; } ``` 兩個函數的實現都比較常見。每種情況下,我們都是從源對象(rhs)到目的對象(*this),簡單的拷貝了Impl的結構體的內容.我們利用了這樣的事實:編譯器會為Impl生成拷貝操作的代碼,這些操作會自動將stuct的內容逐項拷貝,就不需要我們手動來做了。我們因此通過調用Widget::Impl的編譯器生成的拷貝操作符來實現了Widget拷貝操作符。在copy構造函數中,注意到我們遵循了Item 21的建議,不直接使用new,而是優先使用了std::make_unique. 在上面的例子中,為了實現Pimpl做法,std::unique_ptr是我們使用的智能指針類型,因為對象內(在Widget內)的pImpl對對應的實現對象(Widget::Impl對象)擁有獨占所有權。但是,很有意思的是,當我們對pImpl使用std::shared_ptr來替代std::unique_ptr,我們發現本Item的建議不再適用了。沒必要在Widget.h中聲明析構函數,沒有了用戶自己聲明的析構函數,編譯器會很樂意生成move操作代碼,而且生成的代碼表現的行為正合我們意。widget.h變得如下所示: ```cpp class Widget{ //in "widget.h" public: Widget(); ... //no declarations for dtor //or move operations private: struct Impl; //std::shared_ptr std::shared_ptr<Impl> pImpl; //instead of std::unique_ptr }; ``` `#include widget.h`的客戶代碼: ```cpp Widget w1; auto w2(std::move(w1)); //move-consturct w2 w1 = std::move(w2); //move-assign w1 ``` 所有代碼都會如我們所愿通過編譯,w1會被默認構造,它的值會被move到w2,之后w2的值又被move回w1.最后w1和w2都得到析構(這使得指向的Widget::Impl對象被析構) 對pImpl應用std::unique_ptr和std::shared_ptr的表現行為不同的原因是:它們之間支持自定義的deleter的方式不同。對于std::unique_ptr,deleter的類型是智能指針的一部分,這就使得編譯器生成更小的運行時數據結構以及更快的運行時代碼成為可能。更好的效率的結果是要求當編譯器生成特殊函數(如析構以及move操作)被使用時,std::unique_ptr所指向的類型必須是完整的。對于std::shared_ptr來說,deleter的類型不是智能指針的一部分。雖然會造成比較大的運行時數據結構和慢一些的代碼。但是在調用編譯器生成的特殊函數時,指向的類型不需要是完整的。 對于Pimpl做法,在`std::unique_ptr`和`std::shared_ptr`的特點之間,其實并沒有一個真正的權衡。因為Widget和Widget::Impl之間是獨占的擁有關系,`std::unique_ptr`在此項工作中很合適。然而,在一些其他的場景中,共享式擁有關系存在,`std::shared_ptr`才是一個合適的選擇,就沒必要像依靠`std::unique_ptr`這樣的函數定義的做法了。 |要記住的東西| |:--------- | |Pimpl做法通過減少類的實現和類的使用之間的編譯依賴減少了build次數| |對于`std::unique_ptr` pImpl指針,在class的頭文件中聲明這些特殊的成員函數,在class的實現文件中定義它們。即使默認的實現方式(編譯器生成的方式)可以勝任也要這么做| |上述建議適用于`std::unique_ptr`,對`std::shared_ptr`無用|
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看