我的环境
VS2022,通过代码:std::cout << __clang_version__ << std::endl;
,打印的clang版本为:12.0.0
多态的本质
个人的理解:
多态的本质是让我们得以用一种与类型无关(type-independent)的方式来操作这些类对象。
参考 Essential C++ P137
而设计抽象基类的第一个步骤就是找出所有子类共通的操作行为,这些操作行为所代表的便是这个基类的共有接口(public interface)。设计抽象基类的下一步,便是设法找出哪些操作行为与类型相关(type-dependent)——也就是说,有哪些操作行为必须根据不同的派生类而有不同的实现方式。这些操作行为应该成为整个类继承体系中的虚函数(virtual function)。设计抽象基类的第三步,便是试着找出每个操作行为的访问层级(access level)(public、protected、private)。
参考 Essential C++ P145
百度百科:在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
使用虚函数的例子:
#include <iostream>
class Animal
{
public:
virtual void Print() const
{
std::cout << "This is 'Animal' " << std::endl;
}
};
class Dog : public Animal
{
public:
void Print() const override
{
std::cout << "This is 'Dog' " << std::endl;
}
};
void My_Print(const Animal& animal)
{
animal.Print();
}
int main()
{
Dog d;
My_Print(d);
}
不带继承的多态:
#include <iostream>
enum Animal_Type
{
un_set, Dog, cat
};
class Animal
{
public:
Animal(Animal_Type animal_type) : animal_type(animal_type) {}
void Print() const
{
switch (animal_type)
{
case Animal_Type::un_set:
std::cout << "unset!" << std::endl;
break;
case Animal_Type::Dog:
std::cout << "this is Dog" << std::endl;
break;
case Animal_Type::cat:
std::cout << "this is Cat" << std::endl;
break;
default:
break;
}
}
protected:
Animal_Type animal_type;
};
void My_Print(const Animal& animal)
{
animal.Print();
}
int main()
{
Animal dog(Animal_Type::Dog);
My_Print(dog);
}
坏处:难以维护。
C++模板
函数模板
参考:https://www.flipcode.com/archives/Faking_Polymorphism_with_C_Templates.shtml
使用了函数模板。通过函数模板的实参推导(argument deduction)来做到:
#include <iostream>
class Animal
{
public:
void Print() const
{
std::cout << "This is 'Animal' " << std::endl;
}
};
class Dog : public Animal
{
public:
void Print() const
{
std::cout << "This is 'Dog' " << std::endl;
}
};
template<typename A_T>
void My_Print(const A_T& animal)
{
animal.Print();
}
int main()
{
Dog d;
My_Print(d);
}
好处:避开了运行时开销、虚表增加的额外字节;
坏处:模板带来的编译代码膨胀问题。
同时,把他们放入一个容器中会有些问题,因为转为Animal丢失了原本的类型:
#include <iostream>
#include <vector>
class Animal
{
public:
void Print() const
{
std::cout << "This is 'Animal' " << std::endl;
}
};
class Dog : public Animal
{
public:
void Print() const
{
std::cout << "This is 'Dog' " << std::endl;
}
};
class Cat : public Animal
{
public:
void Print() const
{
std::cout << "This is 'Cat' " << std::endl;
}
};
template<typename A_T>
void My_Print(const A_T& animal)
{
animal.Print();
}
int main()
{
Dog d;
Cat c;
My_Print(d);
My_Print(c);
std::vector<Animal> vec;
vec.push_back(std::move(d));
vec.push_back(std::move(c));
for (auto& animal : vec)
{
My_Print(animal);
}
}
打印结果:
CRTP
参考:https://zhuanlan.zhihu.com/p/408668787
CRTP是Curiously Recurring Template Pattern的缩写,中文可以翻成奇异递归模板,它是通过将子类类型作为模板参数传给基类的一种模板的使用技巧,类似下面代码形式:
template<typename T>
class Base {};
class Derived : public Base<Derived> {};
#include <iostream>
template<typename Derived>
class Animal
{
public:
void Print() const
{
static_cast<Derived*>(this)->Print();
}
};
class Dog : public Animal<Dog>
{
public:
void Print() const
{
std::cout << "This is 'Dog' " << std::endl;
}
};
class Cat : public Animal<Cat>
{
public:
void Print() const
{
std::cout << "This is 'Cat' " << std::endl;
}
};
template<typename A_T>
void My_Print(A_T& animal)
{
animal.Print();
}
int main()
{
Dog d;
Cat c;
My_Print(d);
My_Print(c);
}
但是他们其实没有公用一个基类(Dog是 Animal<Dog>
,而Cat则是 Animal<Cat>
),测试程序:
#include <iostream>
template<typename Derived>
class Animal
{
public:
void Print() const
{
static_cast<Derived*>(this)->Print();
}
void PrintCount() const
{
std::cout << count << std::endl;
}
protected:
static int count;
};
template<typename Derived> int Animal<Derived>::count = 0;
class Dog : public Animal<Dog>
{
public:
Dog() { count++; }
void Print() const
{
std::cout << "This is 'Dog' " << std::endl;
}
~Dog() { count--; }
};
class Cat : public Animal<Cat>
{
public:
Cat() { count++; }
void Print() const
{
std::cout << "This is 'Cat' " << std::endl;
}
~Cat() { count--; }
};
template<typename A_T>
void My_Print(A_T& animal)
{
animal.Print();
}
int main()
{
Dog d;
{
Dog d2;
d.PrintCount();
}
Cat c;
d.PrintCount();
c.PrintCount();
}
打印结果:2、1、1,可以看到 Dog 和 Cat 不共用一个基类。
这种模板的方法还有一个弊端,也即无法放入一个容器中。
且由于继承关系,Dog的父类是 Animal<Dog>
,所以在实现 Animal<Dog>
的时候 Dog 还没有声明,所以Animal 的内部是无法获取 Dog 的一些信息的。(存疑)
因此要放入一个容器中目前我知道的只能用正经的虚函数或者用丑陋的非继承的方法。