宣告一個新的 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