模板的应用
动态多态和静态多态
1.1动态多态
我们看下面的代码:
#include <iostream>
#include <memory>
class DynamicGeoObj{
public:
virtual void draw() = 0;
virtual int center_of_line() = 0;
};
class DyCircle :public DynamicGeoObj{
virtual void draw() override{
std::cout << "动态多态画出一个圆" << std::endl;
}
virtual int center_of_line() override{
return 100;
}
};
class DyLine : public DynamicGeoObj{
virtual void draw() override{
std::cout << "动态多态划了一条线" << std::endl;
}
virtual int center_of_line() override{
return 1;
}
};
void DynamicDraw(DynamicGeoObj* dir){
dir->draw();
}
void CalculateDistance(DynamicGeoObj* obj1, DynamicGeoObj* obj2){
std::cout << obj1->center_of_line() - obj2->center_of_line() << std::endl;
}
int main() {
std::unique_ptr<DynamicGeoObj> dy_cricle(new DyCircle());
std::unique_ptr<DynamicGeoObj> dy_line(new DyLine());
DynamicDraw(dy_cricle.get());
DynamicDraw(dy_line.get());
CalculateDistance(dy_cricle.get(), dy_line.get());
return 0;
}
/*
动态多态画出一个圆
动态多态划了一条线
99
*/
上面这段代码是动态多态使用的代码, 使用父类指针调用子类的方法。
1.2静态多态
template<typename GeoObj>
void StaticDraw(const GeoObj* obj){
obj->draw();
}
template<typename GeoObj1, typename GeoObj2>
void StaticCalculateDistance(const GeoObj1* obj1, const GeoObj2* obj2){
std::cout << obj1->center_of_line() - obj2->center_of_line() << std::endl;
}
class StaticCircle{
public:
void draw() const {
std::cout << "静态多态我画了一个圆" << std::endl;
}
int center_of_line() const{
return 100;
}
};
class StaticLine{
public:
void draw() const {
std::cout << "静态多态画了一条线" << std::endl;
}
int center_of_line() const{
return 1;
}
};
int main(){
std::unique_ptr<StaticCircle> st_circle(new StaticCircle());
std::unique_ptr<StaticLine> st_line(new StaticLine());
StaticDraw(st_circle.get());
StaticDraw(st_line.get());
StaticCalculateDistance(st_circle.get(), st_line.get());
}
/*
静态多态我画了一个圆
静态多态画了一条线
99
*/
上面是一个静态多态的例子
动态多态是在运行期间的,而静态多态实在编译期间完成。
动态多态:
- 能够优雅地处理异类集合
- 可执行代码比较小
- 对代码进行完全编译,不需要发布源代码,模板库通常需要同事分发模板实现的源代码
静态多态:
- 很容易实现内建类型的集合,不需要通过公共基类表达接口的共同性
- 代码效率通常比较高,因为不存在指针的间接引用,并且有更多的内联机会
- 安全性更好
trait和policty
trait 萃取
policty 策略
2.1 一个累加序列
template<class T>
auto accumulation(const T* beg, const T* end){
auto ans = T();
while(true){
ans += *beg;
if (beg == end) break;
++beg;
}
return ans;
}
上面的代码看起来很好,但是存在很多问题例如如果我传入一个char数组进入,我们可以发现ans多次累加之后很容易就越界了。 如果多个char字符相加应该用int存储才对,多个int相加应该用long储存才对。可以使用trait
来处理这个问题.
template<class Type>
struct AccumulationTraits;
template<>
struct AccumulationTraits<int>{
typedef long accumulation_type;
static accumulation_type GetZero(){
return 0;
}
};
template<>
struct AccumulationTraits<char>{
typedef int accumulation_type;
static accumulation_type GetZero(){
return 0;
}
};
template<>
struct AccumulationTraits<float>{
typedef double accumulation_type;
static accumulation_type GetZero(){
return 0.0;
}
};
template<class T, typename ty = AccumulationTraits<T>>
auto accumulation(const T* beg, const T* end){
auto ans = ty::GetZero();
while(true){
ans += *beg;
if (beg == end) break;
++beg;
}
return ans;
}
int main() {
int arr[] = {1, 2 , 3 ,4 ,5};
auto cc = accumulation(&arr[0], &arr[4]);
std::cout << cc << std::endl; //15
char str[] = {'3', 'c', 'p'};
std::cout << '3' + 'c' + 'p' << std::endl; //262
auto str_sum1 = accumulation(&str[0], &str[0]);
auto str_sum2 = accumulation(&str[0], &str[2]);
std::cout << str_sum1 << " " << str_sum2 << " " << sizeof(str_sum1) << " " << sizeof(str_sum2) << std::endl; //51 262 4 4
auto str_sum3 = accumulation<char, AccumulationTraits<int>>(&str[0], &str[2]);
std::cout << str_sum3 << " " << sizeof(str_sum3) << std::endl; //262 8
return 0;
}
这里我直接给出 Templates 书中的最后的版本。
2.1 命名模板参数优化
许多模板类有非常多的参数例如:
template <class T1 = BreadPolicy1,
class T2 = BreadPolicy2,
class T3 = BreadPolicy3,
class T4 = BreadPolicy4>
class BreadSlicer{
...
}
但在大多情况下,模板类都有默认的参数,但是如果我想修改其中的一个参数,例如我要修改T3的默认参数那么我就得知道T1,T2,因为输入缺省参数得知道前面所有参数得值。
有以下的编程技巧可以解决这个问题。
核心思路思路是通过一个辅助类PolicySelector
将所有的变量类型封装到其中,下面是步骤
#include <iostream>
class Policy1{
};
class Policy2{
public:
static void do_print(){
std::cout << "Policy2 do print" <<std::endl;
}
};
class Policy3{
static void do_print(){
std::cout << "Policy3 do print" <<std::endl;
}
};
class Policy4{
};
// 假设上面的类使我们最终要传递的模板参数。
class DefaultPolicy{
public:
typedef Policy1 p1;
typedef Policy2 p2;
typedef Policy3 p3;
typedef Policy4 p4;
};
// 首先通过DefaultPolicy 将这4类型都重新命名到一个类中,最终直接通过p1,p2,p3,p4完成操作
class BreadPolicy :virtual public DefaultPolicy{
};
// BreadPolicy 用于作为默认参数
template <class T>
class Policy1_is : virtual public DefaultPolicy{
public:
typedef T p1;
};
template <class T>
class Policy2_is : virtual public DefaultPolicy{
public:
typedef T p2;
};
template <class T>
class Policy3_is : virtual public DefaultPolicy{
public:
typedef T p3;
};
template <class T>
class Policy4_is : virtual public DefaultPolicy{
public:
typedef T p4;
};
//Policy1_is, Policy2_is, Policy3_is, Policy4_is 这四个类中都有p1,p2,p3,p4这四个类型,然后将外部新定义的类型覆盖之前定义的。 这里需要使用虚拟继承,原因后面说
template <class Base, int N>
class Discriminator : public Base{
};
template <class T1,
class T2,
class T3,
class T4>
class PolicySelector : public Discriminator<T1, 1>,
public Discriminator<T2, 2>,
public Discriminator<T3, 3>,
public Discriminator<T4, 4>
{
};
template <class T1 = BreadPolicy,
class T2 = BreadPolicy,
class T3 = BreadPolicy,
class T4 = BreadPolicy>
class BreadSlicer{
public:
void print(){
policies::p2::do_print();
policies::p3::do_print();
}
private:
typedef PolicySelector<T1, T2, T3, T4> policies;
};
class PolicyP2{
public:
static void do_print(){
std::cout << "test policy p2" << std::endl;
}
};
class PolicyP3{
public:
static void do_print(){
std::cout << "test policy p3" << std::endl;
}
};
int main(){
BreadSlicer test;
test.print();
std::cout << std::endl;
BreadSlicer<Policy2_is<PolicyP2>> test1;
test1.print();
std::cout << std::endl;
BreadSlicer<Policy2_is<PolicyP3>, Policy3_is<PolicyP2>> test2;
test2.print();
std::cout << std::endl;
}
在最终的调用这BreadSlicer
中,将传入的所有模板参数重新用一个类保管,所有的参数类型都被封装到了 PolicySelector
该类初始化的时候会依次继承传入的类,所有在上文中BreadPolicy和PolicyP… 都是用的是virtual,这样会避免重复继承。
当使用默认参数得时候,看上去是继承类四次,但实际上
首先BreadSlicer<Policy2_is<PolicyP2>> test1;
这样的传参实际上就是就是传入类第一个参数,其他三个参数用的都是默认值,之后PolicySelector分别继承Discriminator 1,2,3,4, 由于Policy2_is
和 BreadPolicy
都是派生类,它们里面都拥有P1, P2,P3,P4,这样最后在继承Discriminator的时候将新传入的参数覆盖掉了之前的参数。
Policy2 do print
Policy3 do print
test policy p2
Policy3 do print
test policy p3
test policy p2
进程已结束,退出代码0
上述代码运行结果
上面的方法成功解决了填写一个默认参数得问题,但是如果想随意填写两个默认参数,那么就会出现问题,只有
2.2 空基类优化
在C++中如果一个类没有任何成员变量,那么编译器会将其大小设置为1。
但是如果有下面的情况:
template<class T1, class T2>
class Opt{
public:
T1 info1; //模板参数作为成员变量,可能为null, 那么它大小为1会占用一个字节
T2 info2; //又会占用一个字节
};
如果T1和T2都是空类,那么Opt
就会是2个字节,也许我们可以
class opt :private T1, private T2 {
...
}
也许可以这样,但是T2,和T2还有别的可能,如果是int, 或者不是class,那么上面的写法不成立
假设我们知道一个参数必然是class,而另外一个参数不是类,那可以有以下的方法
template<class Base, class type>
class BaseMemberPair : private Base{
private:
type member_;
public:
BaseMemberPair(Base const& base, type member)
: Base(base), member_(member)
{}
const Base& first() const{
return (Base* const &) *this;
}
Base& first() {
return (Base* &) *this;
}
const type& Second() const {
return member_;
}
type& Second() {
return member_;
}
};
template<class T1, class T2>
class Option{
public:
Option(T1& t, T2 val)
:info_and_storage(t, val)
{
}
private:
BaseMemberPair<T1, T2> info_and_storage;
};
将这两个参数封装到BaseMemberPair
中,因为有成员变量的存在,BaseMemberPair
不会为空类,从而消除了可能存在的额外空间使用。
int main(){
std::cout << sizeof(Opt<Empty2, Empty2>) << std::endl;
Empty ss;
Option<Empty, int> cc(ss, 1);
Opt<Empty, int> aa;
std::cout << sizeof(cc) << std::endl;
std::cout << sizeof(aa) << std::endl;
}
/*
2
4
8
*/
最终结果中,使用Option更加省空间, 一个对象省类4bt的空间。在Opt的大小为8, 应该是存在内存对其的问题。不过不管怎么样确实省下了字节