本文所談的對象是帶有指針的類(Class with pointer);這時不能使用編譯器自動合成的三大函數(Big three):拷貝構造、拷貝賦值和析構。需要自己去顯式的定義着三大函數;
primer中給出的建議是:如果一個類需要需要自定義析構函數,幾乎可以肯定它也需要自定義拷貝賦值運算符和拷貝構造函數。
所以一般情況下,這三個函數總是一起自定義,或者一起不定義。
在實際中,如果定義了其他構造函數,那麼最好也提供一個默認構造函數。
1、拷貝構造函數
定義:如果一個構造函數的第一個參數是自身類類型的引用(必須是引用),且任何額外參數都有默認值,則此構造函數是拷貝構造函數。
並且參數幾乎總是const的引用。
對於不含指針的類,合成拷貝構造函數和自定義的拷貝構造函數等價;
拷貝初始化不僅發生在用=定義變量時,下列情況也會發生:
(1)將一個對象作爲實參傳遞給一個非引用類型的形參。
(2)從一個返回類型爲非引用類型的函數返回一個對象。
(3)用花括號列表初始化一個數組中的元素或聚合類中的成員。
對於編譯器自動生成的合成拷貝構造函數,依次拷貝每個成員變量--copies each member varible(nembers,objects,arrays)。
一旦成員裏面有指針,也會拷貝指針,造成的結果就是兩個指針指向同一片內存。最終導致類的析構失敗。
2、拷貝賦值運算符
拷貝賦值其實就是對賦值運算符重載。
對於賦值運算符,必須定義爲成員函數。
賦值運算符通常返回一個指向類對象的引用。
3、析構函數
析構函數釋放對象使用的資源,並銷燬對象的非static數據成員。
隱式銷燬一個內置指針類型的成員不會delete它所指向的對象。
下面的程序來自於--侯捷老師的網絡課程《C++面向對象高級開發》
通過程序詳細的介紹Class with pointer的設計。
string.h
#ifndef __MYSTRING__
#define __MYSTRING__
#define _CRT_SECURE_NO_WARNINGS
class String
{
public:
//默認構造函數,並設置默認值
String(const char* cstr = 0);
//Big Three三大函數:拷貝構造,拷貝賦值,析構。編譯器會自動給出三大函數。
//對於class with pointer,三大函數需要自己寫,不能使用編譯器給的默認版本。
//不然會出現內存泄漏(memory leak)和別名(alias):多個指針指向同一塊內存。
//1. 拷貝構造
String(const String& str);
//2. 拷貝賦值,返回值爲String&,可以滿足連串賦值的操作
String& operator=(const String& str);
//3. 析構
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
//對傳入的指針進行判斷
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}
//若未指定初值
else {
m_data = new char[1]; //爲了搭配上面的寫法
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
//檢查自我賦值(self assignment)
//1. 提高效率
//2. 正確性,如果自我賦值會出錯
if (this == &str)
return *this;
//經典寫法1-2-3
//1. 先清空自己指向的內存
delete[] m_data;
//2. 分配需要的內存
m_data = new char[strlen(str.m_data) + 1];
//3. 最後拷貝數據
strcpy(m_data, str.m_data);
return *this;
}
//深拷貝,編譯器自動給出的爲淺拷貝
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
// <<的重載必須寫爲非成員函數
// 如果寫成成員函數,使用者則需要這樣寫:string << cout; 這樣不符合常用習慣。
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string-test.cpp
#include "string.h"
int main()
{
String str1;
String str2("hello str2");//默認構造
String str3 = "hello str3";//默認構造
String str4(str2);//拷貝構造
String str5 = str4;//拷貝構造,同上
str1 = str2;//賦值構造
//重載<<
cout << "str1:" << str1 << endl;
cout << "str2:" << str2 << endl;
cout << "str3:" << str3 << endl;
cout << "str4:" << str4 << endl;
cout << "str5:" << str5 << endl;
//new:先分配memory,再調用ctor。
String* pstr = new String[3];
//編譯器將上面語句翻譯爲下面三個動作
//1. void* mem = operator new(sizeof(String)*3); 其內部調用malloc(n);
//2. pc = static_cast<String*>(mem);
//3. pc->String::String();調用三次構造
pstr[0] = str1;
pstr[1] = str2;
pstr[2] = str3;
cout << "pstr[1]:" << pstr[0] << endl;
cout << "pstr[2]:" << pstr[1] << endl;
cout << "pstr[3]:" << pstr[2] << endl;
//delete:先調用dtor,再釋放memory;
delete[] pstr;
//編譯器將上面語句翻譯爲下面兩個動作
//1. String::~String(pstr);調用三次析構。
//2. operator delete(pstr); 其內部調用free(pstr);
}