是否曾好奇struct定義的資料結構型別,當我拷貝構造時,或者賦值操作時會發生什麼?倘若我結構中存在指標引用物件時,又能否正確處理?帶著這些疑問,我們來對struct的建構函式進行研究,以解答以下幾個疑問:
1) 何時編譯器會自動為struct合成建構函式
2) 如何能保證攜帶指標引用物件的struct正確拷貝或拷貝構造
讓我們先來看第乙個問題,考慮如下**。serverconfig只有兩個簡單的成員,通過反彙編可見編譯器合成了serverconfig的建構函式,並呼叫其成員的建構函式。若我們移除addr成員,編譯器則不會為serverconfig合成建構函式。由此不難發現,當struct成員存在建構函式時,編譯器會自動為其生成建構函式。
但是值得注意class的預設建構函式不是必須的,也就是說。預設建構函式是編譯器所需要的,它用以保證程式的正確執行,如初始化虛表指標;並非為程式提供預設初始值之類。當class繼承自含預設建構函式的父類時,具有預設建構函式的成員時,存在virtual function時,或者virtual繼承時; 會觸發編譯器合成預設建構函式。
#include #include #include class cstring
cstring(const char *str)
~cstring()
private:
char *m_str;
};typedef struct serverconfig;
int main(int argc, char *argv)
(gdb) disassemble main
dump of assembler code for function main(int, char**):
...0x000000000040065d <+16>: lea -0x20(%rbp),%rax # config1位址放入rax
0x0000000000400661 <+20>: mov %rax,%rdi # 通過rdi傳入this指標
0x0000000000400664 <+23>: callq 0x4006ce # 構造config1
0x0000000000400669 <+28>: mov $0x0,%ebx
0x000000000040066e <+33>: lea -0x20(%rbp),%rax
0x0000000000400672 <+37>: mov %rax,%rdi
0x0000000000400675 <+40>: callq 0x4006ec ...
(gdb) disassemble serverconfig::serverconfig
dump of assembler code for function serverconfig::serverconfig():
...0x00000000004006d6 <+8>: mov %rdi,-0x8(%rbp) # 獲取this指標
0x00000000004006da <+12>: mov -0x8(%rbp),%rax #
0x00000000004006de <+16>: add $0x8,%rax # this + 0x08, 偏移掉port計算得到addr的位址
0x00000000004006e2 <+20>: mov %rax,%rdi #
0x00000000004006e5 <+23>: callq 0x400684 # 呼叫cstring建構函式
...
但是上面的**是危險的,只要我們對serverconfig引用了拷貝構造或者賦值操作時,會引發double free。那這又是為什麼呢?讓我們考慮如下**,編譯後我們進行反彙編。不難發現serverconfig並未合成拷貝建構函式,而是進行了按位拷貝。因此config2拷貝了config1內addr成員攜帶的指標值而非指標引用物件,引起重複釋放。
int main(int argc, char *argv)
(gdb) disassemble main
dump of assembler code for function main(int, char**):
...0x000000000040065d <+16>: lea -0x30(%rbp),%rax # 獲取config1位址
0x0000000000400661 <+20>: mov %rax,%rdi
0x0000000000400664 <+23>: callq 0x4006ea # 構造config1
0x0000000000400669 <+28>: mov -0x30(%rbp),%rax
0x000000000040066d <+32>: mov %rax,-0x20(%rbp) # 拷貝config1.port 到 config2.port
0x0000000000400671 <+36>: mov -0x28(%rbp),%rax
0x0000000000400675 <+40>: mov %rax,-0x18(%rbp) # 拷貝config1.addr.m_str 到 config2.addr.m_str
0x0000000000400679 <+44>: mov $0x0,%ebx
0x000000000040067e <+49>: lea -0x20(%rbp),%rax
0x0000000000400682 <+53>: mov %rax,%rdi
0x0000000000400685 <+56>: callq 0x400708 # 析構config2
0x000000000040068a <+61>: lea -0x30(%rbp),%rax
0x000000000040068e <+65>: mov %rax,%rdi
0x0000000000400691 <+68>: callq 0x400708 # 析構config1
我們現在開始回答第二個問題,如何能保證攜帶指標引用物件的struct正確拷貝或拷貝構造。那就是其含有指標引用之類的成員,都應正確宣告拷貝建構函式和賦值操作函式。如本例中cstring正確宣告如下,這樣編譯器會正確為serverconfig合成拷貝建構函式和賦值操作函式。
class cstring
cstring(const char *str)
cstring(const cstring &cstr)
cstring &operator =(const cstring &cstr)
~cstring()
private:
char *m_str;
};
(gdb) disassemble main
dump of assembler code for function main(int, char**):
...0x0000000000400677 <+42>: callq 0x4007a6 0x000000000040067c <+47>: lea -0x20(%rbp),%rdx
0x0000000000400680 <+51>: lea -0x30(%rbp),%rax
0x0000000000400684 <+55>: mov %rdx,%rsi
0x0000000000400687 <+58>: mov %rax,%rdi
0x000000000040068a <+61>: callq 0x4007e0 ...
當我們明確不允許拷貝的時候,一定要禁止拷貝構造和賦值操作函式。可以繼承如下禁止拷貝基類即可。
class iuncopyable
; private:
iuncopyable(iuncopyable &);
iuncopyable & operator=(const iuncopyable&);
};
C 建構函式深入理解
01 初始化引數列表.cpp include include include using namespace std struct student 拷貝建構函式定義 拷貝建構函式的有效引數,必須是該類的物件的引用 if 1 student student s,int class no 1 else ...
深入理解C 之繼承
目錄 繼承 封裝和多型是物件導向程式設計的重要特性。其成員被繼承的類叫基類也稱父類,繼承其成員的類叫派生類也稱子類。派生類隱式獲得基類的除建構函式和析構函式以外的所有成員。派生類只能有乙個直接基類,所以c 並不支援多重繼承,但乙個基類可以有多個直接派生類。繼承是可以傳遞的。即 如果classb派生出...
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...