如题,为什么派生类用到的动态内存分配(DMA),其就必须定义显示析构函数、复制构造函数和赋值运算符呢?
一、派生类无DMA的情况(基类有DMA)
// lacks.h
class BaseDMA {// Base Class Using DMA
private:
char* label;
int rating;
public:
BaseDMA(const char* l = "null", int r = 0);
void showB() const;
virtual ~BaseDMA();
};
class LacksDMA :public BaseDMA {// derived class without DMA
private:
enum { COL_LEN = 40 };
char color[COL_LEN];
public:
LacksDMA(const char* c = "blank", const char* l = "null", int r = 0);
void showL() const;
};
// lacks.cpp
#include "lacks.h"
#include <iostream>
#include <cstring>
// BaseDMA methods
BaseDMA::BaseDMA(const char* l, int r) {
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
void BaseDMA::showB() const {
std::cout << "Label: " << label << std::endl;
std::cout << "Rating: " << rating << std::endl;
}
BaseDMA::~BaseDMA() {
delete[] label;
}
// LacksDMA methods
LacksDMA::LacksDMA(const char* c, const char* l, int r) : BaseDMA(l, r) {
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
}
void LacksDMA::showC() const {
showB();
std::cout<< "Color: " << color << std::endl;
}
#include "lacks.h"
int main() {
LacksDMA ldma = LacksDMA("red", "Nike", 8);
ldma.showC();
return 0;
}
Label: Nike
Rating: 8
Color: red
二、派生类有DMA的情况(基类有DMA)
// dma.h
class BaseDMA {// Base Class Using DMA
private:
char* label;
int rating;
public:
BaseDMA(const char* l = "null", int r = 0);
void showB() const;
virtual ~BaseDMA();
};
class HasDMA :public BaseDMA {// derived class Using DMA
private:
enum { COL_LEN = 40 };
char color[COL_LEN];
char* style;
public:
HasDMA(const char* c = "blank", const char* s = "null", const char* l = "null", int r = 0);
void showC() const;
};
// dma.cpp
#include "dma.h"
#include <iostream>
#include <cstring>
// BaseDMA methods
BaseDMA::BaseDMA(const char* l, int r) {
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
void BaseDMA::showB() const {
std::cout << "Label: " << label << std::endl;
std::cout << "Rating: " << rating << std::endl;
}
BaseDMA::~BaseDMA() {
delete[] label;
}
// HasDMA methods
HasDMA::HasDMA(const char* c, const char* s, const char* l, int r) : BaseDMA(l, r) {
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
void HasDMA::showC() const {
showB();
std::cout<< "Color: " << color << std::endl;
std::cout<< "Style: " << style << std::endl;
}
#include "dma.h"
int main() {
HasDMA hdma = HasDMA("red", "Running", "Nike", 8);
hdma.showC();
return 0;
}
Label: Nike
Rating: 8
Color: red
Style: Running
在dma.cpp中给派生类添加一个显示析构函数释放堆内存就OK了:
HasDMA::~HasDMA() {
delete[] style;
}
如果涉及调用对象的复制构造函数呢?
// run_dam.cpp
#include "dma.h"
int main() {
HasDMA dma0 = HasDMA("red","Running","Nike",8);//#1 构造函数直接初始化dma0
HasDMA dma1 = dma0;//#2 调用默认【复制构造函数】初始化dma1
return 0;
}
先谈谈类的几种默认函数:
- A(); // 默认构造函数(实例化一个默认成员数据值的对象)
- A(const A&); // 默认复制构造函数(完成基本类型数据类型的拷贝)
- A& operator=(const A&) // 默认赋值运算符(完成基本类型数据类型的拷贝)
- ~A(); // 默认析构函数(什么也不做)
我们看run_dma.cpp中#2这行代码,它们调用了基类和派生类的默认复制构造函数,如下:
>>>>>>>>>>>>>>>> 默认复制构造函数 <<<<<<<<<<<<<<<<<<<<<<
// 基类
BaseDMA::BaseDMA(const BaseDMA& bd) {
label = bd.label;
rating = bd.rating;
}
// 派生类
HasDMA::HasDMA(const HasDMA& hs) : BaseDMA(hs) {
style = hs.style;
<!-- 成员color是数组,不是基本数据类型,所以不处理对它的复制 -->
}
在run_dma.cpp中,#2行: HasDMA dma1 = dma0; 调用默认复制构造函数用dma0初始化dma1,这时char*类型的style被赋值为一个地址,这个地址正是dma0中成员变量style的地址,地址里存的内容没被复制,也就是所谓的浅拷贝。事实上,将以上代码加入到run_dma.cpp中,程序运行到#2行就自动终止了。
如果涉及调用对象的赋值运算符呢?
// run_dma.cpp
#include "dma.h"
int main() {
HasDMA dmaM = HasDMA("red","Running","Nike",8);//#1 构造函数直接初始化dmaM
HasDMA dmaN;//#2 默认构造函数创建对象dmaN
dmaN = dmaM;//#3 调用默认【赋值运算符】将dmaM赋值给dmaN
return 0;
}
其实赋值运算符和复制构造函数的函数体代码是相似的,赋值运算符都被调用了,调用了基类赋值运算符是因为在派生类中显示调用了基类的赋值运算符。
>>>>>>>>>>>>>>>> 默认赋值运算符 <<<<<<<<<<<<<<<<<<<<<<<<<
// 基类
BaseDMA& BaseDMA::operator=(const BaseDMA& bd) {
label = bd.label;
rating = bd.rating;
return *this;
}
// 派生类
HasDMA& HasDMA::operator=(const HasDMA& hs) {
BaseDMA::operator=(hs);// copy base portion
style = hs.style;
<!-- 成员color是数组,不是基本数据类型,所以不处理对它的复制 -->
return *this;
}
加上述代码添加到run_dma.cpp中,程序运行,问题同样出现在析构时。与上述的断点分析是一样的。故:“派生类有DMA,其就必须定义显示赋值运算符(深度复制)”。
总之,原因都是默认的复制构造函数、默认的赋值运算符,没法很好地处理动态内存分配。只有自己去显式地去定义而不是用默认的才能处理得完备,才能不让Bug产生!
现在给出完备的代码:自定义析构函数、自定义复制构造函数、自定义赋值运算符
// dma.h
class BaseDMA {// Base Class Using DMA
private:
char* label;
int rating;
public:
BaseDMA(const char* l = "null", int r = 0);
void showB() const;
virtual ~BaseDMA();
// Add
BaseDMA(const BaseDMA& bd);
BaseDMA& operator=(const BaseDMA& bd);
};
class HasDMA : public BaseDMA {// Derived class Using DMA
private:
enum { COL_LEN = 40 };
char color[COL_LEN];
char* style;
public:
HasDMA(const char* c = "blank", const char* s = "null", const char* l = "null", int r = 0);
void showC() const;
// Add
~HasDMA();
HasDMA(const HasDMA& hs);
HasDMA& operator=(const HasDMA& hs);
};
// dma.cpp
#include "dma.h"
#include <iostream>
#include <cstring>
BaseDMA::BaseDMA(const char* l, int r) {
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
void BaseDMA::showB() const {
std::cout << "Label: " << label << std::endl;
std::cout << "Rating: " << rating << std::endl;
}
// Add destructor
BaseDMA::~BaseDMA() {
delete[] label;
}
// Add copy constructor
BaseDMA::BaseDMA(const BaseDMA& bd) {
label = new char[std::strlen(bd.label) + 1];
std::strcpy(label, bd.label);
rating = bd.rating;
}
// Add assignment operator
BaseDMA& BaseDMA::operator=(const BaseDMA& bd) {
if (this == &bd)
return *this;
delete[] label;// prepare for new style
label = new char[std::strlen(bd.label) + 1];
std::strcpy(label, bd.label);
rating = bd.rating;
return *this;
}
// —————————————————————————————————————————————————————————————————————————————————
HasDMA::HasDMA(const char* c, const char* s, const char* l, int r) : BaseDMA(l, r) {
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
void HasDMA::showC() const {
showB();
std::cout << "Color: " << color << std::endl;
std::cout << "Style: " << style << std::endl;
}
// Add destructor
HasDMA::~HasDMA() {
delete[] style;
}
// Add copy constructor
HasDMA::HasDMA(const HasDMA& hs) : BaseDMA(hs) {
std::strncpy(color, hs.color, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
// Add assignment operator
HasDMA& HasDMA::operator=(const HasDMA& hs) {
if (this == &hs)
return *this;
BaseDMA::operator=(hs);// copy base portion
std::strncpy(color, hs.color, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
delete[] style;// prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
测试代码: (程序完美运行)
#include "stock.h"
int main() {
HasDMA dma0 = HasDMA("red","Running","Nike",8);//#1 构造函数直接初始化dma0
HasDMA dma1 = dma0;// 调用【复制构造函数】将dma1初始化为dma0
HasDMA dma2;// 默认构造函数创建对象dma2
dma2 = dma0;// 调用【赋值运算符】将dma0赋值给dma1
dma2.showC();
return 0;
}