在《高品質編程指南_林銳》的第九章開頭部分有一段話,
"預設的拷貝建構函式 和 預設的賦值函數 均採用 '位拷貝' 而非 '值拷貝' 的方式實現, 倘若類中含有指標變數, 這兩個函數註定將出錯 "
其中吸引我眼球的是 "位拷貝" 這個字眼, E文好像是 "bitwise copy", 全稱"逐位拷貝". 沒看過E文原版的, 這些是網上看到的.
說實話, 拋出一個概念性的東西, 而沒有作一定的解釋, 是很不負責任的行為.
網上的有些人說 '位拷貝' 就是 '淺拷貝', 拷的是地址; '值拷貝' 應該就算是 '深拷貝' 吧, 拷的是內容.
-------------------------------------------------可愛的分割線 -------------------------------------------------------------
其實以上的都是廢話, 在 <C++ Primer 第四版> 中第13章<複製控制>的開頭部分最後一句話:
有一種特殊常見的情況需要類定義自己的複製控製成員的: 類具有指標成員. (複製建構函式, 賦值操作符 和 解構函式總稱為複製控制copy control)
在 13.1.1 合成的複製建構函式(synthesized copy constructor) 中, 合成的複製建構函式就是預設的拷貝建構函式, 他的行為是, 執行逐個成員初始化(menberwise initialize), 將新對象初始化為原對象的副本.
所謂"逐個成員初始化", 指的是編譯器將現有對象的每個非 static 成員, 依次複製到正建立的對象. 只有一個例外, 每個成員的類型決定了複製該成員的含義. 合成複製建構函式直接複製內建類型成員的值, 類類型成員使用該類的複製建構函式進行複製. 數群組成員的複製是個例外, 雖然一般不能複製數組, 但如果一個類具有數群組成員, 則合成複製建構函式將複製數組. 複製數組時合成複製建構函式將複製數組的每一個元素.
如 class ClassA
{
.........
private:
int ia;
int *pb;
}
那麼, 合成複製改造函數如下所示
ClassA::ClassA(const ClassA &orig):
ia(orig.ia), pb(orig.pb)
{}
//寫一個ClassB, 有自己的複製建構函式, 一個ClassC, 它的一個成員變數是ClassB 對象, ClassC c; ClassC d(c); 事實證明一切
看到 pb(orig.pb) 了沒, 指標pb 是指標orig.pb 的一個副本, 但是他們的指向是相同的. *pb 和 *orig.pb 是指向同一個資源, 他們之間任何一個修改都會互相影響. 因為指標會出現這種情況, 所以<c++ primer 第四版>13章開頭才寫了那麼一句話.
而那些什麼 '位拷貝' 和 '值拷貝' 更象是一個混饒我們思維的一個東西.
如果還是要分'位拷貝' 或 '值拷貝', 個人看法, 這個成員變數都是 '值拷貝', 不管是內建類型, 類類型 或 指標, 它們的地址都跟被複製的對象不同, 證明他們都被分配了記憶體空間. 指標成員變數指向什麼地址是另一回事了.
測試例子很簡單, 即不貼了.
-------------------------------------------------可愛的分割線 -------------------------------------------------------------
<C++ Primer 第四版> 15.4.3節
1. 如果衍生類別定義了自己的複製建構函式, 該複製建構函式一般應顯式使用基類複製建構函式初始化對象的基類部分:
class Base{/*..............*/};
class Derived: public Base{
public:
//Base::Base(const Base&) not invoked automatically
Derived(const Derived &d):
Base(d) /* other member initialization */
{/*..............................*/}
};
初始化函數Base(d) 將衍生類別對象d 轉換為它的基類部分的引用, 並調用基類複製建構函式. 如果省略基類初始化函數, 如下代碼:
Derived(const Derived &d)
{/*..............................*/}
效果是運行Base 的 預設建構函式 初始化對象的基類部分. 假定 Derived 成員的初始化從 d 複製對應成員, 則新構造的對象將具有奇怪的配置: 它的 Base 部分將儲存預設值, 而它的Derived 成員是另一對象的副本.
2. 賦值操作符通常與複製建構函式類似: 如果衍生類別定義了自己的賦值操作符, 則該操作符必須對基類部分進行顯式賦值:
//Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
if(this != rhs){
Base::operator=(rhs); //assigns the base part
................................
}
return *this;
}
賦值操作符必須防止自身賦值. 假定左右運算元不同, 則調用 Base 類的賦值操作符給基類部分賦值. 該操作符可以由類定義, 也可以是合成賦值操作符, 這沒什麼關係-----------我們可以直接調用它. 基類操作符將釋放左運算元中基類部分的值, 並賦以來自rhs 的新值. 該操作符執行完畢後, 接著要做的是為衍生類別中的成員賦值.
3. 解構函式的工作與複製建構函式和賦值操作符不同: 衍生類別解構函式不負責撤銷基類對象的成員. 編譯器總是顯式調用衍生類別對象基類部分的解構函式. 每個解構函式只負責清除自己的成員:
//Base::~Base() invoked automatically
Derived::~Derived () {/* do what it takes to clean up derived members */}
對象的撤銷順序與建構函式相反: 首先運行衍生類別解構函式, 然後按繼承層次依次向上調用個基類解構函式.