最近在看元编程中,对虚函数和模板编程有一点点感悟,写一篇博客简单总结一下。
虚函数和模板是C++里面很棒的特征,他们都提供了一种方法,让程序在编译中完成一些计算,去掉的这些计算在比较low的编程方式中,是需要在程序运行中执行的。在这里,我要强调的是:“在编译过程中完成一些计算”。
我会举两个例子,一个是虚函数的,比较简单,另一个例子是关于特征模板的,在例子中,根据模板参数的类型自动选择模板的底层数据结构。
第一个例子是比较简单的虚函数的例子,有很多种水果的类型,我们有一个函数要展示他们的颜色。于是比较low的写法就是这样的:
struct apple {
string color() {
return "red";
}
};
struct pear {
string color() {
return "yellow";
}
};
void showAppleColor(const apple *a) {
cout << a->color() << endl;
}
void showPearColor(const pear *p) {
cout << p->color() << endl;
}
int main () {
apple a;
pear p;
showAppleColor(&a);
showPearColor(&p);
}
但是我们都知道,有了虚函数,这个需求可以这么实现:
struct fruit {
virtual string color() = 0;
};
struct apple : public fruit {
string color() {
return "red";
}
};
struct pear : public fruit {
string color() {
return "yellow";
}
};
void showYourColor(const fruit *f) {
cout << f->color() << endl;
}
int main () {
apple a;
pear p;
showYourColor(&a);
showYourColor(&p);
}
其实这个例子很简单,如果对虚函数比较了解的话,就知道为什么我说"虚函数的方式,是在编译过程中完成了一些计算"。是这样的,继承了有虚函数的基类的派生类,在大部分的编译器实现中,这个类会有一个指针指向一个虚函数表,在编译过程中,编译器往虚函数表填入了真正会执行的"水果类型的函数"的地址。所以,在程序运行中,就可以通过一个基类指针实现派生类的函数调用。如果对虚函数的机制不太了解,给你推荐一篇很棒的博客:http://blog.csdn.net/haoel/article/details/1948051
我所说的“在编译过程中完成一些计算”,也就是指编译器往虚函数表填函数地址的过程。
template<typename T>
struct Container {
Container() {
if(typeid(T) == typeid(int)) {
container = new ibis::bitvector;
} else {
container = new std::vector<T>;
}
}
// 省略析构等其他方法
void* container;
};
template<typename T>
void Container::save(const T &t) {
if (typeid(T) == typeid(int)) { // ** 是不是很恶心?其他方法的实现,都得根据typeid来做if判断
((ibis::bitvector *)(container))->setBit(t, 1);
} else {
((std::vector<T> *)(container))->push_back(t);
}
}
但是,有了特征模板我们可以这么实现:
// 定义两种tag
struct bitvector_tag{};
struct stdvector_tag{};
//存储选择器
//默认使用stdvector作为底层存储
template<typename T> storage_selector {
typename stdvector_tag container_t;
};
//存储选择器
//对于int类型则底层存储用bitvector
template<int> storage_selector {
typename bitvector_tag container_t;
};
template<typename T, typename storage>
struct Container;
//定义stdvector的BasicContainer
template<typename T, stdvector_tag>
struct Container {
std::vector<T> container;
};
//定义bitvector的BasicContainer
template<typename T, bitvector_tag>
struct Container {
ibis::bitvector container;
};
class Book {};
int main() {
Container<int, storage_selector<int> > c1;
Container<Book, storage_selector<Book> > c2;
}在上面的代码中,我们可以看到Container特化了两个版本,template<typename T, stdvector_tag> 和 template<typename T, bitvector_tag> ,只要在用户代码中使用storage_selector,就可以选择特化版本,从而“自动”选择底层存储。统统这一切都是在编译期完成的,不管在编码难度还是在程序的大小和程序的执行效率,后者都是更加优秀的。
我说的“编译期完成一些计算工作” 指的就是在编译过程中,根据数据类型,选择特化版本的代码,从而避开了typeid的if选择以及很多其他不必要的工作。
这样的例子,在c++的源码里面很多很多,比如basic_string或者iterator,都有很漂亮的实现。
虚函数和模板真是个很棒的东西,正确的使用可以带来代码良好的设计。
在编译过程完成一些计算工作,让代码更加简洁、性能更高。另外,模板元编程更是非常充分地利用了编译器的计算,值得好好研究。
原文地址:http://blog.csdn.net/answer3y/article/details/38776265