2019年2月19日 星期二

c++ 類型的複製型建構式與指定型運算式的差異與被呼叫的時機

寫 c++ 程式時, 當自訂的類型(class)如果沒有指標(*)的成員, 基本不用寫複製型建構式(copy constructor, 當建構式為 類型(類型 &) 的型式)或指定型運算式(copy assignment, 也就是函式為 類型 &operator =(類型 &) 的型式), 編譯器(compiler)會自動完成. 一旦有了指標的成員, 並在建構式中動態分配空間給指標使用時,  就必須在解構式(destructor)中釋放指標所佔用的空間(資源), 並且在複製型建構式中, 除了要動態分配空間給指標使用外, 也要將來源的資源一一加以複製, 甚至有些時候還要完成移動型建構式(Move Constructor)及移動型指定式(Move assignment), 否則可能造成無法編譯或是記憶體洩漏(memory leak),嚴重時會因資源共用,導致記憶體重複釋放的危機.  編譯器針對類型的 = 運算, 會去呼叫複製型建構式或指定型運算式(括一般型指定運算及移動型指定運算), 端看物件是否建構完成, 因此要特別注意資源分配及釋放的時機, 例如:
            auto   copy = *this;
上式的 copy, 在指定運算 = 之前, 物件尚未形成, 因此沒有任何方法可用, 所以編譯器呼叫的是複製型建構式(建構並複製)來構製物件,並非類型中的指定型運算式, 但如果將上式分成兩行來寫, 物件事先建構完成(參考下列的 assign 變數), 之後便能利用 = 運算式去指定產生新物件(這時編譯器用的才是指定型建算式), 參考以下範例:
            // aa.c
            #include <stdio.h>
            class  A { // 自訂的新類型
                int   *resource; // 指標成員, 初始化前不得使用
                int    size;           // 一般的整數成員             
                public:
                void Init (int n=16) {  // 初始化方法, 預設參數 n=16
                        size = n;      // 儲存空間的記憶量
                        resource = new int[size]; //  動態分配記憶空間, 初始化指標 resource
               } // init method
                ~A( ) {  // 0. 解構式, 解除資源
                        printf("deconstuctor\n");
                        if( resource != nullptr )  delete resource;
                }// deconstructor
                A ( ) {  // 1. 基本建構式, 陣列建構也是用此式
                    printf("Init constuctor\n");
                    Init( ); // 初始化資源
                } // basic constructor
                A  (A  &a ) {  //2. 複製型建構式, 簡稱複建式
                    printf("Copy constuctor\n");
                    Init( ); // 初始化資源
                    for(int i=0; i < size; i++ ) resource[i] = a.resource[i];
                }  // Copy constructor
                A  (A  &&a ) {  // 3. 移動型建構式, 簡稱移動式
                    printf("Move constuctor\n");
                    resource = a.resource; // 資源換手
                    size = a.size;                   // 複製一般成員
                    a.resource = nullptr;    // 避免資源被釋放
                }  // Move constructor
                A & operator = (A &a) { //4. 一般指定型運算式,
                     printf("Copy assignment\n");
                     if( &a != this ) {
                          resource = a.resource; // 資源共享(resource 不再另外分配空間)
                          size = a.size;                    // 但還是要複製一般成員
                     }
                     return *this; // 返回物件本身
                 }  // Copy assignment

                A & operator = (A &&a) { // 5. 移動型指定運算式,
                     printf("Move assignment\n");
                     if( &a != this )  {
                              if( resouce !=nullptr )  delete resouce;//避免記憶體洩漏
                              resource = a.resource; // 資源換手
                              size = a.size;                    // 仍要複製一般成員
                              a.resource = nullptr;    // 避免資源被釋放
                      }
                      return *this; // 返回物件本身
                 }  // Move assignment                 
                void testMethod() {      // 測試方法
                       A  copy = *this;       //  will call Copy constructor
                       A  assign;                 //  assign construct ready.
                       assign = *this;         // will call Copy assignment
                } // some test method
            };  //  class customize
int main( ) {
      A a;
      a.testMethod( );
}
用 g++ aa.c && ./a.out 編譯並執行:
Init constuctor     <==  a 建構完成
Copy constuctor  <==  copy   建構複製
Init constuctor     <==  assign 建構
Copy assignment <==  assign 指定
deconstuctor        <==  assign.resource 釋放
deconstuctor        <==  copy.resource 釋放
deconstuctor        <==  a.resource 釋放

            

沒有留言: