分类: C/C++

C++实现成员函数检查

最近看到一段代码,感觉非常trick,但也非常有意思,写出来记录一下。

背景是这样的,有一个模板函数 copy_assign ,其作用非常简单,就是将第二参数“拷贝”给第一个参数,但是为了对能够进行深拷贝的类型进行深拷贝,希望的行为是这样的:

如果T有成员函数int assign(const T &),则调用dest.assign(src),并以assign函数的返回值作为返回值;
如果T没有成员函数int assign(const T &),则调用dest=src,并返回0。

函数的原型如下:

并且为了降低运行时开销,我们希望这一切是在编译期确定的,所以我们需要在编译期就能够确定类型T是否有assign成员函数,并且根据结果指定对应的行为。

如何判断一个类有没有特定成员函数?


首先要解决的第一个问题是:在模板类的代码部分,我们并不知道类型T是否有我们想要的成员函数,据我所知C++也没有提供这样的机制来判断,那该怎么解决这个问题呢?

我们必须要在编译期利用C++的一些机制让编译器在不报错退出的情况下完成我们的目的,下面定义的模板类就是 __has_assign__ 用来做这件事情的:

代码着实有些trick,需要细细品味。这个模板类针对类型参数T的实例化的静态变量value的值,就代表了类型T中是否有我们想要的assign函数。

代码的关键步骤在函数的最后三行,声明了两个模板函数chk,而第二个函数是总是可以匹配上T的,这就利用了C++模板匹配中的一点规则:当有多个可行的匹配时,编译器总会选择更“紧”的匹配(更特例化)。那么什么情况下第一个函数声明会是一个匹配呢?答案在于type_check这个模板类,它接受两个相同的类型参数,而传入的第一个是我们想要的assign函数的类型,第二个参数是模板类型参数的成员函数assign(如果有的话),也就是说,如果传入的类型T是一个具有成员函数 int assign(const T &) 的类型,则第一个chk会成为一个更“紧”的匹配被编译器选中,这样静态变量value就会被确定为true,目标达成。

如何判断变量是不是类的实例?


上面这个函数只能对自定义类型使用,那C++的基本类型怎么办呢?我们当然也需要支持基本类型的拷贝。很简单:

直接把value设为false就可以了。

如何用一个函数同时适用于类和基本类型?


但上面说的这两个模板类明显是冲突的,不能同时使用,那怎么办呢?

我们可以借助C++模板的偏特例化机制,把这两个模板类通过一个bool类型的参数区分开,形成同一个模板类的两种特例化形式:

这个模板类怎么用呢?对于一个类型 someClass 来说(可以是基础类型),我们可以通过 __has_assign__<__is_class(someClass), someClass>::value 来判断它有没有我们想要的assign函数。 __is_class 是gcc提供的编译期类型判断原语中的一个,见这里

实现copy_assign


有了这个神奇的类,我们终于可以实现文章开头提到的 copy_assign 函数了。可是有一个问题,上面代码最后得到了一个表示有没有assign成员函数的bool类型,但是编译期是没有办法使用bool类型的变量值来改变行为的啊?

既然变量不行,那就用类型。

再声明一个模板类,用来把bool类型的变量转变为类型:

该模板类只有一个bool类型的非类型模板参数c,用这个模板类,我们可以把一个bool值转化成对应的类型了!有了不同的类型,再结合C++的重载机制,我们就可以在编译期完成这样的工作了:

针对最后一个参数的类型不同,编译器会在两个重载的模板函数中选择合适的函数实例化,最后达到了我们的目的。

感慨一下


我觉得能写出这样C++代码的人,一定是对C++有着非常深入的了解,真的可以说是精通C++了。而我等小菜,连读起来都费劲,只能望其项背啊。

据说C++的元编程是完备的,真的是太神奇了!但另一方面,C++也真的是太难学了!