Bootstrap

模板与继承

模板的应用

动态多态和静态多态

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
*/

上面是一个静态多态的例子
动态多态是在运行期间的,而静态多态实在编译期间完成。
动态多态

  1. 能够优雅地处理异类集合
  2. 可执行代码比较小
  3. 对代码进行完全编译,不需要发布源代码,模板库通常需要同事分发模板实现的源代码

静态多态

  1. 很容易实现内建类型的集合,不需要通过公共基类表达接口的共同性
  2. 代码效率通常比较高,因为不存在指针的间接引用,并且有更多的内联机会
  3. 安全性更好

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_isBreadPolicy 都是派生类,它们里面都拥有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, 应该是存在内存对其的问题。不过不管怎么样确实省下了字节

;