虚函数和模板编程的一点共性和特征模板的一个例子

最近在看元编程中,对虚函数和模板编程有一点点感悟,写一篇博客简单总结一下。

虚函数和模板是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> Container;

当T的类型是int的时候,Container采用ibis::bitvector作为底层存储,如果是其他类型,那么采用std::vector。先看一个比较low的实现:

template<typename T>
struct Container {
  Container() {
    if(typeid(T) == typeid(int)) {
      container = new ibis::bitvector;
    } else {
      container = new std::vector<T>;
    }
  }
  // 省略析构等其他方法
  void* container;
};

上面这个设计是可以编译通过和执行的,原理是我们是在构造函数的过程中,通过typeid来选择底层的存储类型。但是这一切都是在运行过程中完成的,噩梦很多,当你现其他方法的过程中,比如存取数据的操作,你的所有代码统统都得根据typeid来选择你的代码分支。比如:

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,都有很漂亮的实现。

虚函数和模板真是个很棒的东西,正确的使用可以带来代码良好的设计。

在编译过程完成一些计算工作,让代码更加简洁、性能更高。另外,模板元编程更是非常充分地利用了编译器的计算,值得好好研究。

时间: 2024-11-01 18:50:39

虚函数和模板编程的一点共性和特征模板的一个例子的相关文章

C++模板编程 - 第四章 非类型模板参数

一个例子是 1 template<typename T, int MAXSIZE> 2 class Stack {}; 在这里我就想起了C语言是怎么弄数据结构的,不得不说模板是很方便的东西.上面的例子是一个类模板,函数模板其实也是类似的. 浮点数和类对象是不允许作为非类型模板参数的. 对上面这句话的补充:这是历史原因,C++ Templates的作者认为C++在未来可能会允许使用浮点数和类对象作为非类型模板参数. 不太好理解的是这个例子 1 template<char const * n

C++程序设计POJ》《WEEK6 多态与虚函数》《编程填空》

#include <iostream> using namespace std; class A { public: A() { } virtual void func() { cout << "A::func" << endl; } virtual void fund() { cout << "A::fund" << endl; } void fun() { cout << "A::

asp编程实例:通过表单创建word的一个例子

先创建一个表单,随便存一个名字好了.例如:上海治疗阳痿医院 xxx.html 〈form action="word_create.asp"〉 Name: 〈input type="text" name="Name" size="50" maxlength="100"〉 Email: 〈input type="text" name="Email" size="

Hello,C++(5)纯虚函数和抽象类

纯虚函数和抽象类 下面通过一个例子来说明纯虚函数的定义方法 在这个类当中,我们定义了一个普通的虚函数,并且也定义了一个纯虚函数.那么,纯虚函数是什么呢??从上面的定义可以看到,纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”. 纯虚函数的实现原理 本节从虚函数表的角度来说明纯虚函数的实现原理. 上面就是我们在前面课程讲到的多态的实现原理,在讲这一部分的时候,讲到了虚函数表以及虚函数表指针.如果我们定义了Shape这样的类,那么,Shape类当中,因为有虚函数和纯虚函数,所以

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

C++基础(纯虚函数与抽象类)

C++基础之纯虚函数与抽象类 引言 纯虚函数在C++编程中的地位很重要,其关联到了设计模式中"接口"的概念. 语法 纯虚函数的语法: 1.  将成员函数声明为virtual 2.  后面加上 = 0 3.  该函数没有函数体 1 class <类名> 2 { 3 virtual <类型><函数名>(<参数表>) = 0; 4 - 5 }; 例如: 1 class CmdHandler 2 { 3 virtual void OnComman

虚函数与虚继承小结

  虚函数的作用就是实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数:实现方法就是在函数返回值之前加上关键字"virtual":如下: #include <stdio.h> class A { public: void fn() { printf("fn in A\n"); } virtual void v_fn() { printf("virtual fn in A\n"); } }; class B : p

虚函数-构造函数-析构函数

在C++里面,虚函数的作用就是  实现 多态 构造函数可以是  虚函数,但是这样做没有多大意义,特别是在有继承关系的时候估计就不行了,没有继承关系的时候,这个类就不会被创建,编译应该是没有问题的, 析构函数 在有继承的时候,经常用虚函数,因为在  子类有实例的时候,如果让父亲的指针指向  子类的实例,而子类的实例中有new了新的地址空间,那么调用父亲的析构函数,如果父亲的析构函数不是虚的,那么只负责把父亲自己的指针回收,但是子类中new就没有回收:而如果析构函数是虚函数,那么首先是释放子类申请的

C++基础篇--虚函数原理

虚函数算是C++最关键和核心的内容之一,是组件的基础.下面先列出一些相关名词,再围绕它们举例说明虚函数的本质实现原理. 基础概念(英文部分来自C++编程思想) 1)绑定:Connectinga function call to a function body is called binding.(把函数调用和函数实现关联的过程) 2)早绑定:Whenbinding is performed before the program is run (by the compiler and linker