前言
好久不写纯技术类的文章了。最近在阅读AMD的推理框架MIGraphX的源码(https://github.com/ROCmSoftwarePlatform/AMDMIGraphX),学习到了一个新的概念:类型擦除(Type Erasure)(嘿嘿,其实类型擦除很多年前就有了,孤陋寡闻了)。一开始在阅读源码的时候,就非常疑惑,明明这两个类型完全没有关系,怎么可以直接赋值然后就可以实现以前只能通过虚函数实现的多态功能呢?后来通过研究发现,这里面其实是使用了类型擦除机制实现了多态,在MIGraphX中有很多需要实现多态功能的类型都通过类型擦除机制实现的,比如struct operation,struct target,struct context等。我们先看一个通过虚函数实现的多态示例,然后看看如何修改该示例通过类型擦除来实现多态。
通过虚函数实现多态
#include<stdio.h>
#include<string>
using namespace std;
// 抽象基类(接口)
class Animal
{
public:
Animal(){}
virtual ~Animal(){}
virtual void Sleep() = 0;
virtual void Walk() = 0;
};
class Cat:public Animal
{
public:
Cat(){}
virtual ~Cat(){}
virtual void Sleep()
{
printf("Cat Sleep\n");
}
virtual void Walk()
{
printf("Cat Walk\n");
}
};
class Dog:public Animal
{
public:
Dog(){}
virtual ~Dog(){}
virtual void Sleep()
{
printf("Dog Sleep\n");
}
virtual void Walk()
{
printf("Dog Walk\n");
}
};
void TestAnimal(Animal *animal)
{
animal->Sleep();
animal->Walk();
}
int main(int argc,char *argv[])
{
Animal *animal;
Dog dog;
Cat cat;
animal=&dog;
TestAnimal(animal);
animal=&cat;
TestAnimal(animal);
return 0;
}
下面是类图:
运行结果:
上面这个示例通过C++中的虚函数实现了多态,这也是C++多态最常见,也是应用最广泛的一种实现方式。这种方式也是C#和java中实现多态最常见的方式(C#和java中通过抽象方法来实现),但是这种方式在某些情况下有很多缺点,其中最大的缺点就是继承增加了程序各个模块之间的耦合度。下面我们来看看如何通过类型擦除实现上述示例中的多态功能。
通过类型擦除实现多态
先直接上代码,下面的代码就是使用类型擦除机制实现了上述示例的多态功能。
#include<stdio.h>
#include<string>
using namespace std;
// 非模板的抽象基类定义接口
class AnimalBase
{
public:
AnimalBase(){}
virtual ~AnimalBase(){}
virtual const std::type_info& type() const = 0;// 查询类型
virtual AnimalBase *clone() const = 0; // 拷贝
// 所有具体子类(比如Dog,Cat)需要实现的接口
virtual void Sleep() = 0;
virtual void Walk() = 0;
};
// 模板子类拥有真正的类型和数据
template<typename ValueType>
class AnimalTemplate : public AnimalBase
{
public:
AnimalTemplate(const ValueType & _value): value(_value)
{
}
public:
virtual const std::type_info& type() const
{
return typeid(value);
}
virtual AnimalBase *clone() const
{
return new AnimalTemplate(value);
}
virtual void Sleep()
{
value.Sleep();
}
virtual void Walk()
{
value.Walk();
}
public:
ValueType value;
};
// 类型无关类
class Animal
{
public:
// 构造函数
Animal():animal(nullptr){}
template <typename T>
Animal(T const& _animal):animal(new AnimalTemplate<T>(_animal)){}
Animal(Animal const& _animal): animal(_animal.animal ?_animal.animal->clone() : nullptr)
{
}
virtual ~Animal()
{
if(animal!=nullptr)
{
delete animal;
animal=nullptr;
}
}
// 赋值
template <typename T>
Animal & operator = (T & _animal)
{
if(animal!=nullptr)
{
delete animal;
animal=nullptr;
}
animal = new AnimalTemplate<T>(_animal);
return *this;
}
void Sleep()
{
assert(animal);
animal->Sleep();
}
void Walk()
{
assert(animal);
animal->Walk();
}
const std::type_info& type() const
{
return animal ? animal->type() : typeid(std::nullptr_t);
}
private:
AnimalBase *animal; // 基类指针,实现多态
};
class Cat
{
public:
Cat(){}
virtual ~Cat(){}
void Sleep()
{
printf("Cat Sleep\n");
}
void Walk()
{
printf("Cat Walk\n");
}
};
class Dog
{
public:
Dog(){}
virtual ~Dog(){}
void Sleep()
{
printf("Dog Sleep\n");
}
void Walk()
{
printf("Dog Walk\n");
}
};
void TestAnimal(Animal animal)
{
animal.Sleep();
animal.Walk();
}
int main(int argc,char *argv[])
{
Animal animal;
Dog dog;
Cat cat;
animal=dog;
TestAnimal(animal);
animal=cat;
TestAnimal(animal);
return 0;
}
运行结果:
上述示例非常清楚的展示了类型擦除机制是如何实现多态的。这里实现类型擦除机制关键的几点:
- 非模板的抽象基类定义接口
- 模板子类拥有真正的类型和数据
- 类型擦除类使用了抽象基类的指针作为成员变量
这里要注意一点:子类必须实现抽象基类AnimalBase中的纯虚函数Sleep()和Walk(),否则赋值会报错,也就不能实现多态
上面的示例我们可以看出,通过类型擦除实现多态可以让程序结构更加简洁,降低程序的耦合度。