【C++泛型编程】编译期可转换性和继承性检测

本文介绍了一种在编译期检测类型转换可能性的方法,利用sizeof和函数重载特性,实现类型T是否能转换为类型U的判断。进一步,通过模板特化技术,实现了对类间继承关系的检测。
        问题:在设计模板函数和模板类时,面对两个型别T和U,我们怎么判断U是否继承自T呢?如果能在编译器判断这样的关系,可以优化泛型编程的函数。在编译器发现这样的关系,就意味着可以不使用dynamic_cast,它会损耗执行效率。
         继承性检测可以转化成一个更一般的问题:检测任意型别T是否可以自动转化成型别U?
 解决方法:可以使用运用sizeof和函数重载的方法来检测这种可转换性。编写两个重载函数:一个接受U(U代表转换目标)的函数,另一个接受“任何型别”的函数。用型别为T的暂时对象来调用这些重载函数,而“T是否可以转化为U”是我们想知道的。如果接受U的那个函数被调用,就可以判断T可以转换为U;否则T无法转换为U。为了知道哪个函数被调用,这两个重载函数以不同大小的型别返回,用sizeof来区分其大小。
 具体方法如下:
 第一步:定义两个不同大小的型别:
 typedef char Small;
  class Big{char dummy[2];};
并且很明显sizeof(Small)<sizeof(Big);
第二步:定义两个重载函数
Small Test(U);
Big Test(...); //可以接受任何其他对象的函数
现在我们可以传递一个T对象给Test()函数,并用sizeof来判断其返回值的大小。
const bool convExists=sizeof(Test(T()))==sizeof(Small);
T()会调用T对象的默认构造函数,万一T的默认构造函数定义为private,那么T()就会编译失败,所以需要换一种方法来生成对象,用函数的返回值来实现,如下:
T MakeT();//不实现
const bool convExists=sizeof(Test(MakeT()))==sizeof(Small);
下面用类模板封装起来:
template<class T,class U>
class Conversion
{
typedef char Small;
class Big{char dummy[2]};
static Small Test(U);
static Big Test(...);
static T makeT();
public:
enum{exists=sizeof(Test(MakeT()))==sizeof(Small)};


};
测试代码如下:
Conversion<double,int>::exists;

现在在Conversion中定义两个常数:
a.exists2Way:表示T和U之间是否可以双向转换。

b.sameType:如果T和U是相同的型别,这个值为true;

template<class T,class U>
class Conversion
{
typedef char Small;
class Big{char dummy[2]};
static Small Test(U);
static Big Test(...);
static T makeT();
public:
enum{exists=sizeof(Test(MakeT()))==sizeof(Small)};
enum{exists2Way=exists&&Conversion<U,T>::exists};
enum{sameType=false};
};

下面通过Conversion的偏特化来定义sameType的版本
template<class T>
class Conversion<T,T>
{
public:
enum{exists=1,exists2Way=1,sameType=1};
};

判断两个类是否存在继承关系,可以用如下的方式:
#define SUPERSUBCLASS(T,U) \
(Conversion<const U *,const T *>::exists&& \        //这里函数调用参数次序交换了,表示U是否可以转换为T,即判断T是父类,U是子类。
 !Conversion<const T*,const void*>::sameType)

如果U是public继承自T,或T和U是同一个型别,那么SUPERSUBCLASS(T,U)返回true.

当SUPERSUBCLASS(T,U)对const U* 和const T *作可转换性检测时,只有三种情况const U *可以隐式的转换为const T*:
 a.T和U是同一个型别
 b.T是U的一个public base
 c.T 是void 

第三种情况可以在前面的!Conversion<const T*,const void*>::sameType处理掉。第一和第二种情况需要用下面更严谨的方法:
#define SUPERSUBCLASS_STRICT(T,U) \
(SUPERSUBCLASS(T,U)&& \
!Conversion<const T,const U>::sameType)
<< C++编程是一种利用模板(Templates)机制进行程序设计的方法,它允许编写与类型无关的代码。这种技术提高了代码复用率,并且避免了强制类型转换带来的风险。 ### 编程介绍 #### 1. 模板的基本概念: 模板是一段通用代码或模式,它可以应用于任何数据类型而无需修改源码本身。根据用途的不同,模板分为两类: - **函数模板**:用于定义一组功能相同的函数。 ```cpp template <typename T> T max(T a, T b) { return (a > b) ? a : b; } ``` 在这个例子中`max()`是能够处理任意类型的比较大小操作的一个函数模板。 - **类模板**:为创建一系列相关的类提供蓝图 ```cpp template<typename T> class Stack{ private: std::vector<T> elems; // elements public: void push(T const& elem){ elems.push_back(elem); } void pop(){ if(elems.empty()) throw std::out_of_range("Stack<>::pop(): empty stack"); elems.pop_back(); } T top()const{ if(elems.empty()) throw std::out_of_range("Stack<>::top(): empty stack"); return elems.back(); } }; ``` 这个栈类能适应多种数据结构形式如int、double等不同类型的数据元素。 #### 2. 使用场景及优势: 使用编程可以减少重复劳动,在保证效率的同时让程序更易维护扩展。例如上述中的容器stack可以用作存储各种不同种类的数据项(整数、字符串甚至自定义对象),这大大简化了软件开发过程并增强了可读灵活。 --- ### 解释原因 之所以要引入编程是因为传统的基于继承的方式存在诸多限制,比如当需要对多个类型执行相同的操作时就必须复制粘贴大量相似但又稍有不同的逻辑;另外强转也可能导致错误难以排查等问题。因此通过将算法抽象出来使之独立于特定类型之外就显得尤为重要。 此外值得注意的是虽然Java也有类似的generics支持但是它的本质只是编译期检查运行时不保留具体信息所以能上可能略逊一筹(C++则是真正的类型实例化)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值