您的位置:首页 > 编程语言 > C语言/C++

C++ 继承和动态内存分配

2017-08-03 00:00 204 查看
摘自《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的派生类设计赋值运算符时,必须给类的每个成员都提供赋值运算符,而不仅仅是新的

原则:

当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符,都必须使用相应的基类方法来处理基类元素。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++