摘要
现在用C++实现多态的常用方法是通过继承和虚函数。但是使用模板同样可以实现多态。
使用继承和虚函数来实现多态(称作动态的多态)存在以下几个设计上的问题:
1. 增加了复杂度
2. 增加代码大小以及程序运行时间
3. 降低程序的灵活性
使用模板来实现多态的话可以解决这些设计问题。
使用继承和虚函数实现的多态(动态的多态)
实现过程为:
A. 识别抽象概念
B. 在抽象基类中将共有方法声明为虚函数
C. 在各个派生类中实现这些共有方法
例如:
class File {
public:
virtual void Open() = 0;
virtual void Save() = 0;
virtual void SaveAs(String&) = 0;
virtual void Close() = 0;
};
class TextFile : public File {
public:
void Open();
void Save();
void SaveAs(String&);
void Close();
};
class ImageFile : public File {
public:
void Close();
void Save();
void SaveAs(String&);
void Close();
};
// 通过菜单项打开文件
void menu_open_file(File* f_ptr) {
f_ptr->Open();
....
....
}
我们可以如下运用上述代码:
File *file_ptr = new TextFile();
menu_open_file(file_ptr);
// 打开文本文件
....
....
file_ptr = new ImageFile();
menu_open_file(file_ptr);
// 打开图片文件
总结:
A. 在上面这个实例中,File就是我们识别的抽象概念。我们将其定义为一个抽象基类。
B. Open, Save, SaveAs, Close是共有方法,并在File类中被声明为虚函数
C. File抽象概念的具体实现是image file和text file。我们为其提供了具体的实现。
D. Open, Save, SaveAs, Close等菜单操作将会调用这里的具体实现
E. 如果用户选择了打开文件菜单项,该菜单项的实现函数将会根据所需打开文件的不同类型调用不同的实现。
使用模板来实现多态
使用模板的话,你不用在基类中声明共有的方法。但是,你需要在程序中隐式声明它们。
使用模板来实现上面的实例:
class TextFile {
void Open();
};
class ImageFile {
void Open();
};
// 使用菜单项来打开文件
template<typename T> void menu_open_file(T file) {
file.open();
}
对上述代码的运用:
TextFile txt_file;
ImageFile img_file;
menu_open_file(txt_file); // 打开文本文件
menu_open_file(img_file);
// 打开图片文件
总结:
A. 只需定义具体的类,如text file和image file,而不用定义抽象类。
B. 将使用这些类的函数定义为模板:如menu_open_file
C. 在模板中,不必使用指针以及引用。
使用动态的多态(也就是利用继承和虚函数)存在的一些问题
时间以及内存使用上的开销
使用动态的多态不能很好的实现容器类。因为这种方法即耗时间又耗内存。利用模板的话就不会有这个问题。C++的标准模板库就是一个很好的例子。
下面这个容器类是用继承来实现的。在C++中加入模板之前,这种方法一直是最常用的。
class container {
virtual void add();
virtual void remove();
virtual void print();
};
class list : container {
....
};
class vector : container {
....
};
void Sort(container& con) {
....
}
void Search(container& con) {
....
}
使用继承来实现多态还会降低程序的灵活性
对基类接口中的共有方法进行增删改是一件即费事又容易出错的事情。有时,这还会引起一系列其他的问题。如果你想在接口上有更大的灵活性的话,更好的方法是使用模板。
class File {
public:
virtual void Open() = 0;
virtual void Save() = 0;
virtual void SaveAs(String& file_name) = 0;
};
class TextFile : public File {
public:
void Open();
void Save();
void SaveAs(String& file_name);
};
class ImageFile : public File {
public:
void Open();
void Save();
void SaveAs(String& file_name);
};
class BinaryFile: public File {
public:
void Open();
void Save();
void SaveAs(String& file_name);
};
假定你想向上面这个文件抽象中添加打印功能。这里的问题是不能向一个二进制文件中添加打印功能,因为不应该允许打印一个二进制文件。
一个解决方法是使用RTTI。但这种方法也不好,它需要对现有的代码作出很多的改变。不然的话,你必须将这些类分为两类:可打印的和不可打印的。
使用模板作为解决之道
class TextFile {
public:
void Open();
void Save();
void SaveAs(String& file_name);
void Print();
};
class ImageFile {
public:
void Open();
void Save();
void SaveAs(String& file_name);
void Print();
};
class BinaryFile {
public:
void Open();
void Save();
void SaveAs(String& file_name);
// 这里没有提供Print函数
};
template <typename T>
void On_Open( T file) {
file.Open();
}
template <typename T>
void On_Save( T file) {
file.Save();
}
template <typename T>
void On_Print( T file) {
file.Print();
}
TextFile txt_file;
ImageFile img_file;
BinaryFile bin_file;
On_Print(txt_file);
On_Print(img_file);
On_Print(bin_file); // 这里编译时将会出错
对二进制文件的打印将会在编译时出现错误。
总结
A. 这两种方法都有自己优劣。
B. 用基于模板的方法来实现多态更好。
C. 根据以下目标来选择这两种方法:可重用性,灵活性,以及所需的性能。
静态的多态(基于模板的方法)相对于动态的多态(基于虚函数的方法)有以下优势:
1. 类型安全
2. 执行速度更快
3. 不必在基类中声明公共接口
4. 低耦合,因此提高了重用性
动态的多态(基于虚函数的方法)相对于静态的多态(基于模板的方法)又有以下优势:
1. 可执行程序的代码相对较小
2. 不需公布源码