EasyC++,繼承和動態(tài)內存分配
繼承和動態(tài)內存分配
今天這篇文章來聊聊繼承與動態(tài)內存分配。
這里面有一個問題,當我們的基類使用動態(tài)內存分配,并且重新定義賦值和復制構造函數,這會對派生類的實現有什么影響呢?
我們來看兩種情況。
派生類不用new
假設基類中使用了動態(tài)內存分配:
- class baseDMA {
- private:
- char *label;
- int rating;
- public:
- baseDMA(const char* l="null", int r=0);
- baseDMA(const baseDMA& rs);
- virtual ~baseDMA();
- baseDMA &operator=(const baseDMA& rs);
- };
在這個聲明里包含了構造函數、析構函數、復制構造函數和重載賦值運算符。
現在假設我們從baseDMA派生出了類lackDMA,但是后者不使用new:
- class lackDMA: public baseMDA {
- private:
- char color[40];
- public:
- ...
- };
問題來了,我們要不要給lackDMA這個類定義析構函數、復制構造函數和賦值運算符呢?
答案是不需要。
首先是析構函數,這個很好想明白,如果我們沒有定義析構函數,那么編譯器會自動定義一個不執(zhí)行任何操作的默認析構函數。實際上派生類的析構函數往往會在執(zhí)行一些邏輯之后調用基類的構造函數,因為lackDMA類中的成員不是通過new創(chuàng)建的,因此不需要額外的操作,所以默認析構函數是合適的。
同樣的默認復制構造函數也會執(zhí)行非new創(chuàng)建成員的復制,所以對于color變量來說是沒問題的。并且在派生類當中,默認復制構造函數除了會復制非new創(chuàng)建的成員之外,還會調用基類的復制構造函數來復制父類成員的部分。所以,對于派生類lackDMA來說,我們使用默認的復制構造函數一樣沒有問題。
賦值也是一樣的,默認的賦值運算符也會自動使用基類的賦值運算符來對基類的成員進行賦值。
派生類使用new
我們再來看看派生類當中使用了new的情況。
- class hasDMA: public baseMDA {
- private:
- char *style;
- public:
- ...
- };
在hasDMA這個類當中,我們添加了一個需要使用new創(chuàng)建的char*成員。在這種情況下,我們就沒辦法使用默認的函數了,就必須定義顯式析構函數、復制構造函數和賦值運算符了,我們一個一個來看。
首先是析構函數,派生類的析構函數會自動調用基類的析構函數,所以我們只需要在析構函數當中釋放派生類中獨有的成員變量即可。
- hasDMA::~hasDMA() {
- delete []style;
- }
然后我們再來看看拷貝構造函數,由于派生類不能訪問基類private成員,所以我們需要調用基類的拷貝構造函數。
- hasDMA::hasDMA(const hasDMA& hs): baseDMA(hs) {
- style = new char[std::strlen(hs.style) + 1];
- std::strcpy(style, hs.style);
- }
最后是賦值運算符,同樣,由于派生類不能訪問基類中私有成員,我們也需要借助基類的賦值運算符:
- hasDMA &hasDMA::operator(const hasDMA& hs) {
- if (this == &hs) return *this;
- baseDMA::operator=(hs);
- delete []style;
- style = new char[std::strlen(hs.style) + 1];
- std::strcpy(style, hs.style);
- return *this;
- }
這當中有一個語句看起來有些奇怪:
- baseDMA::operator=(hs);
這是我們手動顯式調用了基類的賦值運算符,我們直接用等于號賦值也有同樣的效果:
- *this = hs;
為什么不這么干呢?這是因為編譯器在執(zhí)行的時候會默認調用子類的賦值運算符hasDMA::operator=,從而導致一直遞歸導致死循環(huán)。
所以我們需要手動寫明作用域解析符,表明這是調用的父類賦值運算符,而非派生類的運算符,這一點比較隱晦,要千萬注意。