一、虚函数
struct Foo;
typedef struct FooVtb1{
void (* const func0)(struct Foo * pThis);
void (* const func1)(struct Foo * pThis);
void (* const func2)(struct Foo * pThis);
} FooVtb1;
static FooVtb1 foo_vtb1 = {func0_impl,func1_impl,func2_impl};
typedef struct Foo{
const int count;
const FooVtb1 * const pTtv1;
}Foo;
Foo foo0 = {0,&foo_vtb1};
Foo foo1 = {1,&foo_vtb1};
Foo foo2 = {2,&foo_vtb1};
二、非虚函数
typedef struct Foo{
int count;
void (* const func0)(struct Foo * pThis);
void (* const func1)(struct Foo * pThis);
void (* const func2)(struct Foo * pThis);
} Foo;
假设这里需要增加一个将count复位为0的函数,这时增加一个虚函数倒也可以:
typedef struct Foo{
int count;
void (* const func0)(struct Foo * pThis);
void (* const func1)(struct Foo * pThis);
void (* const func2)(struct Foo * pThis);
void (* reset_counter)(struct Foo * pThis);
} Foo;
假如知识将count恢复为0,就没有必要动态地根据对象不同改变函数行为了。这种情况下,也可以单独使用一个函数来实现。
void reset_foo_counter(struct Foo * pThis)
{
pThis->count = 0;
}
这里由于没有使用函数指针,所以能够节省内存,但另一方面,也就意味着无法根据对象来动态地改变行为了。此外如果是C语言的话还有一个命名空间问题。函数命名为reset_foo_counter表明该函数是Foo使用的函数,如果简单地以
reset_counter命名的话,碰巧Bar结构体也有一个名为count的成员和一个复位函数reset_counter,则会冲突。
如果结构体内持有函数指针,就不用考虑这个问题了。结构体Foo中的reset_counter和结构体Bar中的reset_counter 会被当作不同的函数处理。在函数名前加上一个static修饰符可以避免命名冲突。此外,相比非虚函数,使用函数指针的方式更加容易测试。
那么什么时候应该使用非虚函数,什么时候应该使用函数指针呢?这是很难回答的问题。一般来说,经验欠缺的人很容易过度使用非虚函数。一般情况下,优先使用函数指针,只有在内存非常有限,对象行为不会变化的情况下,或者是非常有把握的情况下才考虑使用非虚函数。