摘自《c++ Primer Plus》第6版 13.7
如果基类使用的动态内存分配,并重新定义了赋值和复制构造函数,这将怎样影响派生类的实现?
情况1:派生类不使用new
假设基类在构造函数中使用new,析构函数、复制构造函数和重载赋值运算符也做了相应处理。现在,从base类派生出一个类,这个派生类不使用new,只是包含一些新的数据而已。那么就不需要为派生类定义显式析构函数、复制构造函数、赋值运算符。
- 默认析构函数合适吗?合适,因为派生类没有执行任何特殊的new操作,所以先调用派生类的默认析构函数,再调用基类的析构函数,很ok。
- 复制构造函数合适吗?合适。以前介绍过,默认复制构造函数执行的成员浅拷贝对动态内存分配是不合适,但对于没有new操作的派生类是合适的。对于派生类复制,派生类的复制构造函数使用显示的基类构造复制构造函数来赋值派生类对象的基类部分数据。因此,默认复制构造函数对于新的派生类成员是合适的。
- 同理,默认赋值运算符也合适。
派生类对象的这些属性也适用于包含其他类对象成员的类。例如实现Stock
类时,可以用string
类而不是char *
来存储公司名称。众所周知string
类有采用动态内存分配,而Stock
的默认构造函数不会产生问题,我们现在知道了原因——默认构造函数使用string
的复制构造函数来复制company
成员,Stock
的默认赋值运算符使用string
的赋值运算符来给company
对象赋值,而Stock
的析构函数会自动调用string
类的析构函数。
情况2:派生类使用new
在这种情况下,必须为派生类定义显示析构函数、复制构造函数、赋值运算符。
//包含动态内存分配的类继承
#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();
复制构造函数:baseDMA(const baseDMA & rs);
重载赋值运算符:baseDMA & operator=(const baseDMA & rs);
基类使用new,派生类不使用new
lacksDMA类,没有使用动态内存分配,所以无需提供特殊方法。
基类/派生类析构函数
这里有两个派生类,派生类析构函数会自动调用基类的析构函数,所以各自的职责就是对派生类构造函数执行的工作进行清理。hasDMA
类释放style
,baseDMA
类释放label
。
派生类如何访问基类的友元?
作为派生类的友元,<<
重载函数,不是基类的友元,那怎么访问基类成员label
和rating
呢?答案是使用baseDMA::operator<<()
。因为友元不是成员函数,不能通过作用域解析运算符来指示要使用哪个函数,所以这里的处理方法是使用强制类型转换,以便通过匹配正确原型来使用正确的函数。
复制构造函数也用上了成员初始化列表
hasDMA
类的复制构造函数只能访问派生类的数据,所以它必须调用baseDMA
的复制构造函数来处理共享的基类数据.
特别需要注意的一点:派生类的赋值运算符
baseDMA::operator=(hs);
实际上该语句的语义是:*this = hs;
也就是说,使用基类的赋值运算符,来复制派生类对象的基类部分的数据。为使用了new的派生类设计赋值运算符时,必须给类的每个成员都提供赋值运算符,而不仅仅是新的。
原则:
当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符,都必须使用相应的基类方法来处理基类元素。