C++设计模式之SFINAE:用来检测类中是否有某个成员函数

简介: 针对类中特定成员函数的检测其实在工作中也可能用到。C++中可以用SFINAE技巧达到这个目的。

针对类中特定成员函数的检测其实在工作中也可能用到。C++中可以用SFINAE技巧达到这个目的。


640.png


SFINAESubstitution Failure Is Not An Error的缩写,直译为:匹配失败不是错误。属于C++模板编程中的高级技巧,但属于模板元编程中的基本技巧。当然我其实也并不是C++元编程方面的专家,只是搜集过一些常见的实现方式,然后做过一些测试。在这个过程中,我发现有些常见的SFINAE写法是有问题的,下面探讨一下。


举个例子,我们来check一下C++标准库的类中有没有push_back()成员函数。在C++11之前,可以这样写,经过测试是没有问题的:


#include <iostream>
#include <map>
#include <list>
#include <set>
#include <string>
#include <vector>
struct has_push_back {
    template <typename C, void (C::*)(const typename C::value_type&)>
    struct Helper;
    template <typename C, void (C::*)(typename C::value_type)>
    struct Helper2;
    template <typename C>
    static bool test(...) {
        return false;
    }
    template <typename C>
    static bool test(Helper<C, &C::push_back>*) {
        return true;
    }
    template <typename C>
    static bool test(Helper2<C, &C::push_back>*) {
        return true;
    }
};
int main() {
    std::cout << has_push_back::test<std::list<int> >(NULL) << std::endl;
    std::cout << has_push_back::test<std::map<int, int> >(NULL) << std::endl;
    std::cout << has_push_back::test<std::set<int> >(NULL) << std::endl;
    std::cout << has_push_back::test<std::string>(NULL) << std::endl;
    std::cout << has_push_back::test<std::vector<int> >(NULL) << std::endl;
    return 0;
}


SFINAE实现方式有很多种,细节处可能不同。


两个Helper类的模板参数中。第二个参数为 push_back的函数指针类型。之所以弄了两个Helper,是因为std::string的push_back的参数为char。也就是value_type类型。而其他STL容器。则是const value_type&。所以才用了两个Helper。如果是检测其他成员函数,比如size则不需要这么麻烦只要一个Helper即可。


而test函数,对于返回true的模板函数,其参数是一个指针类型。所以实际check的时候,传入一个NULL就可以匹配到。


C++11之后:


#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>
template <typename>
using void_t = void;
template <typename T, typename V = void>
struct has_push_back:std::false_type {};
template <typename T>
struct has_push_back<T, void_t<decltype(std::declval<T>().push_back(std::declval<typename T::value_type>()))>>:std::true_type {};
int main() {
    std::cout << has_push_back<std::list<int>>::value << std::endl;
    std::cout << has_push_back<std::map<int, int>>::value << std::endl;
    std::cout << has_push_back<std::set<int>>::value << std::endl;
    std::cout << has_push_back<std::string>::value << std::endl;
    std::cout << has_push_back<std::vector<int>>::value << std::endl;
    return 0;
}


C++11简洁了许多。如果需求是要检测任意成员函数,而不限定是哪个函数的话,毫无疑问,需要借助宏了。将上面的代码改变成宏的版本,push_back作为宏的一个参数,即可。


我这里为什么用push_back()举例呢?因为网上能找到的各种SFINAE的实现版本中,很多对于push_back的检测都是有问题的。 而以上列举这两种,都能准确检测出string、vector、list中的push_back()当然C++11之前的版本,需要你能枚举出push_back的各种参数种类才行,若待检测的成员函数重载版本比较多的时候,则可能很麻烦。所以还是C++11之后的版本简洁且通用


下面列举一个常见但某些情况下会存在问题SFINAE范本:


class Base {
};
class Drive:Base {
public:
    void hello() {}
};
template <typename T>
struct has_hello {
    typedef char Yes[1]; // 或     typedef int8_t Yes;
    typedef char No[2];  // 或     typedef int16_t No;
    template <typename>
    static No& has(...);
    template <typename C>
    static Yes& has(decltype(&C::hello));
    static const bool value = sizeof(has<T>(NULL)) == sizeof(Yes);
};
int main() {
    std::cout << has_hello<Base>::value << std::endl;
    std::cout << has_hello<Drive>::value << std::endl;
}


OK,这个用来检测类中是否有hello成员函数是可以的。但是改变成push_back的版本则有问题。


#include <iostream>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>
// 错误的示范
template <typename T>
struct has_push_back {
    typedef char Yes[1];
    typedef char No[2];
    template <typename>
    static No& has(...);
    template <typename C>
    static Yes& has(decltype(&C::push_back));
    static const bool value = sizeof(has<T>(NULL)) == sizeof(Yes);
};
int main() {
    std::cout << has_push_back<std::list<int> >::value << std::endl;
    std::cout << has_push_back<std::map<int, int> >::value << std::endl;
    std::cout << has_push_back<std::set<int> >::value << std::endl;
    std::cout << has_push_back<std::string>::value << std::endl;
    std::cout << has_push_back<std::vector<int> >::value << std::endl;
    return 0;
}


上面这个是一个典型的有问题的案例——常见范本改变的push_back检测,对上面这几个类,只有string能判断为true。vector、list都check有误。


该版本也有很多其他变种。所谓变种主要是在has的返回值、value的判断方面做改编。也有一定问题,具体大家自己测试吧。

相关文章
|
17天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
60 30
|
6天前
|
并行计算 Unix Linux
超级好用的C++实用库之线程基类
超级好用的C++实用库之线程基类
12 4
|
10天前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
14 3
|
10天前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
|
6天前
|
C++ Windows
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
|
6天前
|
C++
2合1,整合C++类(Class)代码转换为MASM32代码的平台
2合1,整合C++类(Class)代码转换为MASM32代码的平台
|
6天前
|
存储 运维 监控
超级好用的C++实用库之日志类
超级好用的C++实用库之日志类
12 0
|
1月前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
1月前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
2月前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
下一篇
无影云桌面