内容简介:C++实现成员函数检查
最近看到一段代码,感觉非常trick,但也非常有意思,写出来记录一下。
背景是这样的,有一个模板函数,其 copy_assign 作用非常简单,就是将第二参数“拷贝”给第一个参数,但是为了对能够进行深拷贝的类型进行深拷贝,希望的行为是这样的:
如果T有成员函数int assign(const T &),则调用dest.assign(src),并以assign函数的返回值作为返回值;
如果T没有成员函数int assign(const T &),则调用dest=src,并返回0。
函数的原型如下:
template < typename T > inline int copy_assign ( T & dest , const T & src ) ;
并且为了降低运行时开销,我们希望这一切是在编译期确定的,所以我们需要在编译期就能够确定类型T是否有assign成员函数,并且根据结果指定对应的行为。
如何判断一个类有没有特定成员函数?
首先要解决的第一个问题是:在模板类的代码部分,我们并不知道类型T是否有我们想要的成员函数,据我所知C++也没有提供这样的机制来判断,那该怎么解决这个问题呢?
我们必须要在编译期利用C++的一些机制让编译器在不报错退出的情况下完成我们的目的,下面定义的模板类就是 __has_assign__ 用来做这件事情的:
template <typename T> struct __has_assign__ { typedef int (T::*Sign)(const T &); typedef char yes[1]; typedef char no[2]; template <typename U, U> struct type_check; template <typename _1> static yes &chk(type_check<Sign, &_1::assign> *); template <typename> static no &chk(...); static bool const value = sizeof(chk<T>(0)) == sizeof(yes); };
代码着实有些trick,需要细细品味。这个模板类针对类型参数T的实例化的静态变量value的值,就代表了类型T中是否有我们想要的assign函数。
代码的关键步骤在函数的最后三行,声明了两个模板函数chk,而第二个函数是总是可以匹配上T的,这就利用了C++模板匹配中的一点规则: 当有多个可行的匹配时,编译器总会选择更“紧”的匹配(更特例化) 。那么什么情况下第一个函数声明会是一个匹配呢?答案在于type_check这个模板类,它接受两个相同的类型参数,而传入的第一个是我们想要的assign函数的类型,第二个参数是模板类型参数的成员函数assign(如果有的话),也就是说,如果传入的类型T是一个具有成员函数 int assign ( const T & ) 的类型,则第一个chk会成为一个更“紧”的匹配被编译器选中,这样静态变量value就会被确定为true,目标达成。
如何判断变量是不是类的实例?
上面这个函数只能对自定义类型使用,那C++的基本类型怎么办呢?我们当然也需要支持基本类型的拷贝。很简单:
template <typename T> struct __has_assign__ { static bool const value = false; };
直接把value设为false就可以了。
如何用一个函数同时适用于类和基本类型?
但上面说的这两个模板类明显是冲突的,不能同时使用,那怎么办呢?
我们可以借助C++模板的偏特例化机制,把这两个模板类通过一个bool类型的参数区分开,形成同一个模板类的两种特例化形式:
template<bool, typename T> struct __has_assign__; template <typename T> struct __has_assign__<true, T> { typedef int (T::*Sign)(const T &); typedef char yes[1]; typedef char no[2]; template <typename U, U> struct type_check; template <typename _1> static yes &chk(type_check<Sign, &_1::assign> *); template <typename> static no &chk(...); static bool const value = sizeof(chk<T>(0)) == sizeof(yes); }; template <typename T> struct __has_assign__<false, T> { static bool const value = false; };
这个模板类怎么用呢?对于一个类型 someClass 来说(可以是基础类型),我们可以通过 __has_assign__ < __is_class ( someClass ) , someClass > :: value 来判断它有没有我们想要的assign函数。 __is_class 是gcc提供的编译器类型判断元语的一个,见 这里 。
实现copy_assign
有了这个神奇的类,我们终于可以实现文章开头提到的 copy_assign 函数了。可是有一个问题,上面代码最后得到了一个表示有没有assign成员函数的bool类型,但是编译期是没有办法使用bool类型的变量值来改变行为的啊?
既然变量不行,那就用类型。
再声明一个模板类,用来把bool类型的变量转变为类型:
template <bool c> struct BoolType { static const bool value = c; }; typedef BoolType<false> FalseType; typedef BoolType<true> TrueType;
该模板类只有一个bool类型的非类型模板参数c,用这个模板类,我们可以把一个bool值转化成对应的类型了!有了不同的类型,再结合C++的重载机制,我们就可以在编译期完成这样的工作了:
template <typename T> inline int copy_assign_wrap(T &dest, const T &src, TrueType c) { return dest.assign(src); } template <typename T> inline int copy_assign_wrap(T &dest, const T &src, FalseType c) { dest = src; return 0; } // 此函数用于拷贝赋值 // - 如果T有成员函数int assign(const T &),则调用dest.assign(src), // 并以assign函数的返回值作为返回值; // - 如果T没有成员函数int assign(const T &),则调用dest=src, // 并返回0。 template <typename T> inline int copy_assign(T &dest, const T &src) { return copy_assign_wrap(dest, src, BoolType<__has_assign__<__is_class(T), T>::value>()); }
针对最后一个参数的类型不同,编译器会在两个重载的模板函数中选择合适的函数实例化,最后达到了我们的目的。
感慨一下
我觉得能写出这样C++代码的人,一定是对C++有着非常深入的了解,真的可以说是 精通 C++了。而我等小菜,连读起来都费劲,只能望其项背啊。
据说C++的元编程是完备的,真的是太神奇了!但另一方面, C++真的是太难学了!
浏览: 0
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 常对象与常成员函数
- C++类中的特殊成员函数
- C++类中的特殊成员函数
- C++成员函数的重载,继承,覆盖和隐藏
- Lisp 家族迎来新成员,函数式语言 Lux 是什么?
- Lisp 家族迎来新成员,函数式语言 Lux 是什么?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ruby on Rails实践之路
沃哈 / 科学 / 2010-5 / 48.00元
《Ruby on Rails实践之路:写给PHP和Java开发者的书》内容简介:Ruby on Rails是基于MVC模式的Web框架,用于开发基于数据库的Web应用。Ruby on Rails中内含了所需的Web服务器WEBrick。该框架配置的数据库除了缺省的MySQL外,还可以是Oracle、SQL Server等其他数据库。《Ruby on Rails实践之路:写给PHP和Java开发者的......一起来看看 《Ruby on Rails实践之路》 这本书的介绍吧!