C++ 繼承和動態內存分配

摘自《c++ Primer Plus》第6版 13.7ios

若是基類使用的動態內存分配,並從新定義了賦值和複製構造函數,這將怎樣影響派生類的實現?c++

狀況1:派生類不使用new

假設基類在構造函數中使用new,析構函數、複製構造函數和重載賦值運算符也作了相應處理。如今,從base類派生出一個類,這個派生類不使用new,只是包含一些新的數據而已。那麼就不須要爲派生類定義顯式析構函數、複製構造函數、賦值運算符。函數

  1. 默認析構函數合適嗎?合適,由於派生類沒有執行任何特殊的new操做,因此先調用派生類的默認析構函數,再調用基類的析構函數,很ok。
  2. 複製構造函數合適嗎?合適。之前介紹過,默認複製構造函數執行的成員淺拷貝對動態內存分配是不合適,但對於沒有new操做的派生類是合適的。對於派生類複製,派生類的複製構造函數使用顯示的基類構造複製構造函數來賦值派生類對象的基類部分數據。所以,默認複製構造函數對於新的派生類成員是合適的。
  3. 同理,默認賦值運算符也合適。

派生類對象的這些屬性也適用於包含其餘類對象成員的類。例如實現Stock類時,能夠用string類而不是char *來存儲公司名稱。衆所周知string類有采用動態內存分配,而Stock的默認構造函數不會產生問題,咱們如今知道了緣由——默認構造函數使用string的複製構造函數來複制company成員,Stock的默認賦值運算符使用string的賦值運算符來給company對象賦值,而Stock的析構函數會自動調用string類的析構函數。this

狀況2:派生類使用new

在這種狀況下,必須爲派生類定義顯示析構函數、複製構造函數、賦值運算符。spa

//包含動態內存分配的類繼承
#ifndef _DMA_H_
#define _DMA_H_
#include<iostream>
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);
	friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs);
};
class lacksDMA :public baseDMA
{
private:
	enum { COL_LEN = 40 };
	char color[COL_LEN];
public:
	lacksDMA(const char * c = "blank", const char * l = "null", int r = 0);
	lacksDMA(const char * c, const baseDMA & rs);
	friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs);
};
class hasDMA :public baseDMA
{
private:
	char * style;
public:
	hasDMA(const char * c = "none", const char * l = "null", int r = 0);
	hasDMA(const char * s, const baseDMA & rs);
	hasDMA(const hasDMA & hs);
	~hasDMA();
	hasDMA & operator=(const hasDMA & hs);
	friend std::ostream & operator<<(std::ostream & os, const hasDMA & hs);
};
#endif
#include<cstring>
baseDMA::baseDMA(const char *l, int r)
{
	label = new char[std::strlen(l) + 1];
	std::strcpy(label, l);
	rating = r;
}
baseDMA::baseDMA(const baseDMA & rs)
{
	label = new char[std::strlen(rs.label) + 1];
	std::strcpy(label, rs.label);
	rating = rs.rating;
}
baseDMA::~baseDMA()
{
	delete[] label;
}
baseDMA & baseDMA::operator=(const baseDMA & rs)
{
	if (this == &rs)
		return *this;
	delete[]label;
	label = new char[std::strlen(rs.label) + 1];
	std::strcpy(label, rs.label);
	rating = rs.rating;
	return *this;
}
std::ostream & operator<<(std::ostream & os, const baseDMA &rs)
{
	os << "Label: " << rs.label << std::endl;
	os << "Rating: " << rs.rating << std::endl;
	return os;
}
lacksDMA::lacksDMA(const char * c, const char * l, int r)
	:baseDMA(l,r)
{
	std::strncpy(color, c, 39);
	color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs)
	:baseDMA(rs)
{
	std::strncpy(color, c, COL_LEN - 1);
	color[COL_LEN - 1] = '\0';
}
std::ostream & operator<<(std::ostream & os, const lacksDMA & rs)
{
	os << (const baseDMA &)rs;
	os << "Color: " << rs.color << std::endl;
	return os;
}
hasDMA::hasDMA(const char * c, const char * l, int r)
	:baseDMA(l, r)
{
	style = new char[std::strlen(c) + 1];
	std::strcpy(style, c);
}
hasDMA::hasDMA(const char * s, const baseDMA & rs)
	:baseDMA(rs)
{
	style = new char[std::strlen(s) + 1];
	std::strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA & hs)
	:baseDMA(hs)
{
	style = new char[std::strlen(hs.style) + 1];
	std::strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
	delete[]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;
}
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
	os << (const baseDMA &)hs;
	os << "Style: " << hs.style << std::endl;
	return os;
}

上述代碼注意點:

基類

基類使用了動態內存分配,因此聲明包含了使用new時所須要的特殊方法:設計

析構函數:virtual ~baseDMA();code

複製構造函數:baseDMA(const baseDMA & rs);對象

重載賦值運算符:baseDMA & operator=(const baseDMA & rs);繼承

基類使用new,派生類不使用new

lacksDMA類,沒有使用動態內存分配,因此無需提供特殊方法。內存

基類/派生類析構函數

這裏有兩個派生類,派生類析構函數會自動調用基類的析構函數,因此各自的職責就是對派生類構造函數執行的工做進行清理。hasDMA類釋放stylebaseDMA類釋放label

派生類如何訪問基類的友元?

做爲派生類的友元,<<重載函數,不是基類的友元,那怎麼訪問基類成員labelrating呢?答案是使用baseDMA::operator<<()。由於友元不是成員函數,不能經過做用域解析運算符來指示要使用哪一個函數,因此這裏的處理方法是使用強制類型轉換,以便經過匹配正確原型來使用正確的函數。

複製構造函數也用上了成員初始化列表

hasDMA類的複製構造函數只能訪問派生類的數據,因此它必須調用baseDMA的複製構造函數來處理共享的基類數據.

特別須要注意的一點:派生類的賦值運算符

baseDMA::operator=(hs);實際上該語句的語義是:*this = hs;也就是說,使用基類的賦值運算符,來複制派生類對象的基類部分的數據。爲使用了new的派生類設計賦值運算符時,必須給類的每一個成員都提供賦值運算符,而不單單是新的

原則:

當基類和派生類都採用動態內存分配時,派生類的析構函數、複製構造函數、賦值運算符,都必須使用相應的基類方法來處理基類元素。

相關文章
相關標籤/搜索