C++实现成员函数检查

栏目: C++ · 发布时间: 7年前

内容简介: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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ruby on Rails实践之路

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实践之路》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器