C++判断一个类是否继承自另一个类

前言

派生类会继承基类的接口,所以我们经常会把一个基类派生出的多个派生类当作同一个类别。在某些函数或者类中,我们可能希望只支持某一类别的输入,即只支持某个类型或者这个类型的派生类,此时就需要判断输入类型是否继承自目标类型。

考虑这样一种情形:线条(Line)由一系列点构成,而“点”可以有多种类型:圆点(Dot)、星点(Star)…,另外还有一种类型“平面”(Plane)则不是一种“点”,它们定义如下:

class Point { };
class Dot : public Point { };
class Star : public Point { };
class Plane { };

template <typename Pt>
class Line { };

显然,线条可以由圆点或星点构成,但不能由平面构成,我们希望 Line 的模板参数 Pt 是一种 Point,如何在代码中进行判断呢?

std::is_base_of

C++11 提供了std::is_base_of(头文件<type_traits>)用于判断两个类型的继承关系,参考cppreference,使用方法为:

std::is_base_of<A, B>::value; // get true if B is derived from A

所以上述问题中,Line可以这么定义:

template <typename Pt>
class Line {
    static_assert(std::is_base_of<Point, Pt>::value,
        "template params MUST be Point!");
};

如果用户使用了错误的类型作为模板参数,比如 Line<Plane> line;,则会在编译时报错。

基类带模板的情况

点的坐标通常可以是多种类型的(int、float、double),下面我们给Point加上模板参数Scalar来支持不同类型数据:

template <typename Scalar>
class Point { };
template <typename Scalar>
class Dot : public Point<Scalar> { };
template <typename Scalar>
class Star : public Point<Scalar> { };
template <typename Scalar>
class Plane { };

这时候你会发现std::is_base_of就不那么好用了,它会要求你提供PointPt的模板参数(即各自的Scalar),而且它会认为Scalar不同的两种 Point 没有继承关系!这往往并不是我们所希望的:

std::is_base_of<Point<float>, Dot<float>>::value; // true
std::is_base_of<Point<float>, Dot<double>>::value; // false


针对这种情况,我在stackoverflow@Jarod42的回答中找到了如下方法:

// case1: T* can cast to C*
template <template <typename...> class C, typename...Ts>
std::true_type is_base_of_template_impl(const C<Ts...>*); 
// case2: T* cannot cast to C*
template <template <typename...> class C>
std::false_type is_base_of_template_impl(...);

template <template <typename...> class C, typename T>
using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T*>()));

其中std::declval<T*>()是在不构造T*指针的情况下获得一个T*指针的引用类型,仅用于类型推断。这个方法利用的是派生类指针能够隐式转换为基类指针,基类C的模板参数则被参数包Ts...捕获,因此支持任意数量的模板参数,而其缺陷是仅适用于 public 继承(protected 或者 private 继承的类指针无法转化为基类指针)。使用方法与std::is_base_of类似:

is_base_of_template<Point, Dot<float>>::value; // true
is_base_of_template<Point, Dot<double>>::value; // true
is_base_of_template<Point, Plane<double>>::value; // false


stackoverflow同一问题下@jenka 的回答提供了适用于 protected 继承的解决方法(其代码好像有点问题,@Anton Dyachenko的补充后才正常)。这个方法能用,但原理我目前还没看明白,先贴出代码吧:

template <template <typename...> class BaseTemplate, typename Derived, typename TCheck = void>
struct test_base_template;

template <template <typename...> class BaseTemplate, typename Derived>
using is_base_of_template = typename test_base_template<BaseTemplate, Derived>::is_base;

//Derive - is a class. Let inherit from Derive, so it can cast to its protected parents
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived {
    template <typename... T>
    static constexpr std::true_type test(BaseTemplate<T...>*);
    static constexpr std::false_type test(...);
    using is_base = decltype(test((Derived*)nullptr));
};

//Derive - is not a class, so it is always false_type
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<!std::is_class_v<Derived>>> {
    using is_base = std::false_type;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值