目錄
1.子類構造函數中初始化父類成員
2.子類顯式調用父類的析構函數
第一種說法:重定義
反駁:
第二種說法:operator~
3.因編譯器版本過低而出現錯誤
貼主在學習C++的繼承時,遇到了很多問題,覺得很變態,特此發帖分享
1.子類構造函數中初始化父類成員
眾所周知,在子類的構造函數中是不能初始化父類成員的,必須調用父類的構造函數來完成初始化
#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():_a(0)//報錯,因為_a是父類成員{cout << "constructor derived \n";}
};
?但有一種情況可能會被誤認為是初始化
#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived()//:base()這里已經隱式調用了base的構造函數了{_a = 0;cout << "constructor derived \n";}
};
這確實可以運行,但這里其實并不是初始化(initialization),而是賦值(assignment),這里看似沒有調用父類的初始化,但其實會隱式調用構造函數
可以看到,base的構造函數還是被調用了,這就是隱式調用了base的構造函數?
那拷貝構造函數呢?若子類不定義默認構造函數的話,拷貝構造時確實會調用父類的拷貝構造函數完成父類函數的初始化
#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}base(const base& b):_a(b._a){cout << "copy constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():base(){cout << "constructor derived \n";}
};int main()
{derived a;cout << "\n";derived b(a);return 0;
}
輸出結果:
?可以看到在拷貝構造時,隱式調用了父類的拷貝構造
但如果子類有用戶定義的拷貝構造函數,如果沒有顯式調用父類的拷貝構造函數,編譯器就會隱式調用父類的構造函數
#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}base(const base& b):_a(b._a){cout << "copy constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():base(){cout << "constructor derived \n";}derived(const derived& d)//子類拷貝構造不顯式調用父類的拷貝構造,會隱式調用父類的構造函數完成父類成員初始化{cout << "copy constructor derived \n";}
};int main()
{derived a;cout << "\n";derived b(a);return 0;
}
輸出結果:
可以看到,在拷貝構造時,編譯器先隱式調用了父類的構造函數
2.子類顯式調用父類的析構函數
眾所周知,子類的析構函數會在被調用完成后自動調用父類的析構函數清理父類成員。因為這樣才能保證子類對象先清理子類成員再清理父類成員的順序。
#include <iostream>
using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){~base();cout << "destructor derived \n";}//會報錯
};int main()
{derived a;return 0;
}
報錯:?
當然,對于99%的情況來說,根本用不到顯式調用析構函數,剩下的1%就是placement new
?管理內存?的情況
對于這里的報錯,我目前知道兩種說法
第一種說法:重定義
這里的~base和~derived構成重定義(隱藏),但他們兩個函數名不相同啊?這是因為經過編譯器處理后所有的析構函數都會被處理成destructor(為了支持多態),那他們兩個析構函數就是重名函數了,父類的析構函數就會被重定義,此時要想讓編譯器知道你要調用的是父類的析構,就要在前面加上作用域限定符
int main()
{cout << int() <<endl;return 0;
}
反駁:
“隱藏”是非限定名查找的一部分。而析構函數的查找方式有所不同,因此不存在隱藏關系。也沒有所謂的“轉換為特殊名稱 destructor(析構函數)”這樣的過程作為名稱查找的目的。?
如果有大佬知道到底對不對的,歡迎評論區解答!!!
第二種說法:operator~
這里的報錯和是不是繼承沒有關系,因為下面代碼也會報同樣的錯誤
class base
{~base(){cout << "~base \n";}void func(){~base();}
};
這段代碼的執行順序其實是先base(),再~
base()會先構造一個base的臨時對象。這里其實不難理解,就例如int(),會調用int的默認構造函數創建臨時的int變量,它的值是0
int main()
{cout << int() <<endl;return 0;
}
輸出:0
所以base()也一樣,會調用base的默認構造函數來構建一個臨時的base對象
然后會試圖調用base對象的operator~重載,如下圖
#include <iostream>
using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}void operator~(){cout << "operator ~ \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};
int main()
{~base();cout <<"\n";base().operator~();//第一行就相當于這么調用return 0;
}
輸出結果:
如上圖,第一個和第一行和第三行的代碼輸出結果相同
所以報錯的原因就是base類沒有operator~重載
流程:先構造一個臨時的base對象,再調用臨時base對象的operator~運算符,最后析構這個臨時的base對象
3.因編譯器版本過低而出現錯誤
?根據第二個問題可以得知,當寫出~base()時,會先構造一個臨時的base對象,然后調用該對象的operator~操作符,最后析構這個臨時base對象。
而如果要直接在子類中調用父類析構函數,可以這樣
class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};
給~base()的前面加上作用域限定符,這樣編譯器就知道你是要調用父類的析構函數,而不是operator~重載。
但如果在父類有operator~重載時,在子類中調用base::~base();呢?
#include <iostream>using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}void operator~(){cout << "operator ~ \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};
int main()
{derived d;return 0;
}
上面代碼在我之前的編譯器中,就會編譯錯誤
error:
cmd /c chcp 65001>nul && C:\mingw64\bin\g++.exe -fdiagnostics-color=always -g "D:\Valkyrie-text\simple text\text.cpp" -o "D:\Valkyrie-text\simple text\text.exe"
D:\Valkyrie-text\simple text\text.cpp: In destructor 'derived::~derived()':
D:\Valkyrie-text\simple text\text.cpp:15:28: error: no matching function for call to 'derived::~derived()'~derived(){base::~base();cout << "destructor derived \n";}^
D:\Valkyrie-text\simple text\text.cpp:7:5: note: candidate: 'base::~base()'~base(){cout << "destructor base \n";}^
D:\Valkyrie-text\simple text\text.cpp:7:5: note: candidate expects 1 argument, 0 provided
后來發現,這是編譯器在抱怨缺少參數
在前面加上thts->就可以了
this->base::~base();
相信又很多人運行上面代碼時都會報和我一樣的錯誤,這是因為編譯器的版本太低
我之前的編譯器是8.1.0的gcc
現在更新了,用14.2.0的版本就不會再報這個錯了,所以強烈建議大家更新一下自己的編譯器!