2018年12月2日 星期日

C++ 類型(class): 建構法(constructor), 解構法(destructor), 動態分配(new), 釋放(delete)

宣告一個新的 c++ 類型時, 如果在類型裏面使用動態記憶空間分配(可以使用 new 運算子或是 malloc( ) 函式傳回指標加以運用),就必須知道 6 種類型的方法及使用時機, 避免造成重複刪除的窘境. 類型裡面的成員分成可記憶的變數與不具記憶的函數(函式的傳回值), 這種在類型裡面的變數稱為狀態(state),而函式則特別稱為方法(method),當類型被引用要產生新物件時第一個被執行的就是類型建構法, 此時裏面變數的狀態都是混沌未明, 透過建構法才能將它初始化,當建構法完成,就會回傳一個該類型的新物件(裏面包含了已初始過的狀態與方法), 因此建構法最後無需再用 return 之類的程式碼. 當物件以指標型式傳進來時就要用箭頭符號引用成員, 如果是物件型式就用句號引用成員, new 回傳的的是物件指標使用 new 所分配的記憶空間必須用 delete 將它釋放. 在方法中使用的 this 就是該物件本身的指標. 指標的存取是一項危險的動作, 一旦指標成為空指標時(null pointer), 不小心產生 Null Pointer Exception(俗稱 NPE), 嚴重的話造成當機.

1. 解構法(destructor),  函式的特徵是 "~類型名稱 ( )", 物件消滅前, 當成員曾經 用new 或 malloc( )分配動態記憶空間時, 要在此要用程式碼 delete 運算子或函式 free( )將它釋放出來.
        ~className ( ) { // Here free all of dynamic resouce
               // delete dynamicResource;
      }

2. 基本建構法(constructor),無傳遞參數(void), 單純初始化內部狀態用,初始類型的陣列也會用此法來建構新物件
      className ( ) {   // Here init all states ...
            // dynamicResource =NULL;
      }  // No need to use return in the end. Do automatically by the compiler

3. 複本建構法(copy constructor), 此函式的特徵是"類型名稱 (物件)",  顧名思義就是要從一份物件來源去產生新類型的物件.例如:
      className (className&  source) { // Here init and copy all state from source
                 // dynamicResource = new className( );
      }   // No need to use return in the end. Do automatically by compiler

4. 物件指定運算(copy assignment), 發動的時機是一個有 = 類型的運算式, 將產生的物件指定給一個變數. 因為運算函式並非建構法, 因此運算函式最後要用 retrun *this 或是 return this; 視傳回的型態(type)而結束. 物件指定運算的特徵是 "類型名稱 operator = (物件)", 在運算函式裏面傳進來的參數必須先判斷非本尊(this) 時, 才需要作指定運算:
      className&  operator = (classNamme  &source) {
              if(   &source != this ) { // this is a pointer, &source is source pointer
                    // Here do copy assignment ...
                    //  dynamicResource = source.dynamicResource; // share dynamicResource
              }
              return *this; // object itself
      }

5. 物件轉移運算(move assignment), 顧名思義就是一個將資產(resource)換手(transfer)的過程,把物件轉交給一個既有物件. 其特徵是 "類型& operator = (類型&& )",其發動的時機也是在一個有 = 類型的運算式, 通常是由編譯器視需求, 先產生一個暫時類型的物件(稱為匿名物件, 也許會透過 copy assignment 產生匿名物件)傳進來, 而受者本身的成員若之前使用 new 分配過動態記憶空間, 就要在此時先釋放,之後將它指向匿名物件的資源,避免記憶體洩漏(memory leak)的情況, 甚至當匿名物件的成員也帶有動態記憶空間時, 也必須最後將該成員設定為 NULL, 當交易完成,匿名物件也會跟著消滅, 這樣才不致在解構時發生重複釋放(double free)記憶體的危機, 當然轉移之前也要先判定來源,避免自我轉移的窘境:
      className& operator = (classNamme&& temp) { // temp is generated by compiler
              if(   &temp != this ) { // this is a pointer, &temp is source pointer
                   // Here do move assignment ...
                   // if(   dynamicResource != NULL ) delete dynamicResource;// free resource
                   // dynamicResource = temp.dynamicResource; // link to resource
                  // temp.dynamicResource = NULL; // temp cut off  the resource
              }
              return *this;  // object itself
      } // temp is destroy automatically by compiler

6. 交易建構法(move constructor), 函式的特徵是 "類型 (類型&& )", 同樣是將資產轉手用的, 只不過是由程式碼主動呼叫(move semantics, 可以透過 std::move(object) 來發動),他是建構法類型其中之一, 編譯器直接將物件轉化成匿名物件交由該建構法運用, 最後另外產生該類型的新物件, 要注意的是物件經轉移後, 其成員狀態, 如果有分配動態記憶空間時, 是否將成員指定為 NULL,視交易建構法程式碼而定,當然也須避免發生重複釋放的危機.
    classNamme(&& temp) { // temp is generated by compiler
         // Here do move assignment ...
        // dynamicResource = temp.dynamicResource; // link to dynamicResource                 
         // temp.dynamicResource = NULL; // Cut off the link of dynamicResource   
     } // temp is destroy automatically by compiler,
7. 範例程式碼:
// hex.c
#include "stdio.h"
#include "iostream"
#include "stdarg.h"
struct hex {
    const char *str;
    int  length;
    char id;
    int  numtable(char c) {  // λ(c)
            if ('0' <= c &&  c <= '9')         return c - '0';
            else if ('a' <=c &&  c <= 'f')  return c - 'a' + 10;
            else if ('A' <=c &&  c <= 'F')  return c - 'A' + 10;
            else return 0;
     };
    char hextable(int i) {  // λ(i)
            char ch;
            if (0 <= i &&  i <= 9)             ch = i + '0';
            else if (10 <= i &&  i <= 15)   ch = i - 10 + 'a';
            else ch = '0';
            return ch;
     };
    ~hex(void)                     { // destructor
        printf("Destroy ~ ");
        if (str) {
            printf("%s ! ",str);
            delete str;
        } else printf("Empty ! ");
        printf("finish\n"); 
     } 
    hex(void)                     { // 1 init constructor
        printf("Init constructor(): \n");
        str = NULL;
        length=0 ;
        id=0;
     }
    hex(hex &a)             {  // 2 copy  constructor     
        printf("Copy Constructor(%s): ", a.str);                
        str = string2hex(a.str); // need a new copy
        printf("finish\n"); 
     }  
    hex(const char *s)        { // 2 copy  constructor
        printf("Copy Constructor(%s)\n",s);
        str = string2hex(s);
     }      
 
    hex(hex &&a)             { // 3 move  constructor
        // printf("Move constructor(%s): ", a.str);
        str = a.str; // just steal
        length = a.length;
        id = a.id;
        a.str = NULL;
        printf("Move Constructor(%s) finish\n",str); 
     }
 
    // Assignment is an operator, need a return type, and must not assign by itself
    hex& operator =(hex &a) { // 4 copy  assignment
        printf("Copy assign = %s ", a.str);
        if( &a != this) {
            str = a.str;// point to the string
            length=0; while (a.str[length++]);// caculate the the length
            id = 0;
            tocstr();    // build a new string
        }
        printf("finish\n");
        return *this; // *this;
     }  
    hex& operator =(char *a){ // 4 copy  assignment
        printf("String assign = %s ", a);
        if( str != a) {
            str = a;    // point to new string
            length=0;    // caculate the the length
            id = 0;
            while (a[length++]);
            tocstr();    // conver to string
        }
        printf("finish\n"); 
        return *this; // *this;
     }
    hex& operator =(hex &&a){ // 5 move  assignment
        printf("Move assign = %s,  self str = %s\n", a.str, str);
        if( &a != this ) { // if pointer of a is not this
            if( str ) delete str;
            str = a.str;
            length = a.length;
            id = a.id;
            a.str = NULL; // to indicate not free a.str when destroy  a
        }
        printf("Move = finish\n"); 
        return *this;
     }

 
    hex& printf(const char *fmt, ...){// to chain printf
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
        return *this; // method chain if necessary
     }     
    hex& tohex(){
        if( id !=1 && length > 0) {
            int  k = length ;
            auto number = new char[(k << 1) + 1];
            int  length = 0;
            for (int i = 0; i < k; i++){
                unsigned char c = str[i];
                number[length++] = hextable((c&0xf0) >> 4);//convert to HEX char, MSB
                number[length++] = hextable(c&0xf);      //convert to HEX char, LSB
             }
            number[length++]=0; //EOS
        }
        return *this;
     }
    hex& tocstr(){
        if( id !=2 && length>0 ) {
            int  k = length;
            auto cstring = new char[(k >> 1) + 1];// include EOS, and each HEX number occupy 2 bytes
            length = 0;
            for (int i = 0; i < k; i += 2) cstring[length++] =  (numtable(str[i]) << 4) + numtable(str[i+1]);
            cstring[length++]=0;//EOS
            str = cstring; 
        }
        return *this;
     }
    const char *string2hex(const char *sx){
        id = 1;
        int k =0 ;
        while (sx[k]) k ++;        // break when EOS
       auto number = new char[(k << 1) + 1];// include EOS, and each HEX number occupy 2 bytes
        length = 0;
        for (int i = 0; i < k; i++){
            unsigned char c = sx[i];
            number[length++] = hextable((c&0xf0) >> 4);//convert to HEX char, MSB
            number[length++] = hextable(c&0xf);      //convert to HEX char, LSB
         }
        number[length++]=0; //EOS
        return number;
     }
    const char *hex2string(const char *number) {
        id = 2;
        int  k = 0 ;
        while (number[k]) k ++;     // break when EOS
        auto cstring = new char[(k >> 1) + 1];// include EOS, and each HEX number occupy 2 bytes
        length = 0;
        for (int i = 0; i < k; i += 2) cstring[length++] =  (numtable(number[i]) << 4) + numtable(number[i+1]);
        cstring[length++]=0;//EOS
        return cstring;
     }    
};
int main(){
    printf("\n1.\n");
    hex aaa;

    printf("\n2.\n");
    auto sss = hex("123"); 
    auto strhex = hex("中文也可以"); 

    printf("\n3.\n");
    sss = strhex.str;
 
    printf("\n4.\n");
    auto bbb = std::move(strhex);

    printf("\nfinal\n");
}
執行 g++ hex.c && ./a.out 輸出結果:
1.
Init constructor():

2.
Copy Constructor(123)
Copy Constructor(中文也可以)

3.
Copy Constructor(e4b8ade69687e4b99fe58fafe4bba5)
Move assign = 653462386164653639363837653462393966653538666166653462626135,  self str = 313233
Move = finish
Destroy ~ Empty ! finish

4.
Move Constructor(e4b8ade69687e4b99fe58fafe4bba5) finish

final
Destroy ~ e4b8ade69687e4b99fe58fafe4bba5 ! finish
Destroy ~ Empty ! finish
Destroy ~ 653462386164653639363837653462393966653538666166653462626135 ! finish
Destroy ~ Empty ! finish

沒有留言: