C++模板知识总结

//参考 《C++必知必会》 条款45 - 60

///
//内容介绍
//1.定义一个普通的模板
//2.模板显式特化
//3.模板局部特化
//4.类模板成员特化
//5.成员模板
//6.模板的模板参数
//7.泛型编程1 嵌入类型信息
//8.泛型编程2 traits
//9.泛型编程3 STL泛型编程惯例
//10.针对类型信息的特化
//11.policy
//12.模板实参推导
//13.测试代码
///

///
//1.定义一个普通的模板
///
template <typename T>
class Heap
{
public:
    void push (const T &value)
    {
        std::cout << "Heap<T>::push" << endl;
    }
    T pop ();
    bool empty () const
    {
        return h_.empty ();
    }

private:
    std::vector <T> h_;
};

///
//2.模板显式特化
///
//针对 const char* 专门写一个显式特化版本
template<>
class Heap <const char*>
{
public:
    void push (const char *pVal) //这里不一定与主模板精确匹配
    {
        std::cout << "Heap<const char*>::push" << endl;
    }
    const char* pop ();
    bool empty () const
    {
        return h_.empty ();
    }

private:
    std::vector <const char*> h_;
};

//另一个显式特化
template<>
class Heap <char*>
{
public:
    void push (char *pVal)
    {
        std::cout << "Heap<char*>::push" << endl;
    }
    char* pop ();
    size_t size () const;
    void capitalize ();
    //未提供 empty();
private:
    std::vector <char*> h_;
};

//可见,主模板和显式特化之间对接口没有任何强制要求。
//不过显式特化最好接口和主模板一致,这样能使泛型函数能使用

///
//3.模板局部特化
///
template<typename T>
class Heap <const T*> //这里部分特化: T ==> const T*
{
public:
    void push (T *pVal)
    {
        std::cout << "Heap<T*>::push" << endl;
    }
    //......
};
//他与显式特化区别:参数未完全确定,仍是一个模板

///
//4.类模板成员特化
///
//主模板:
template<typename T>
class Heap2
{
public:
    void push (const T &value)
    {
        std::cout << "Heap2<T>::push" << endl;
    }
    T pop ();
    bool empty () const { return h_.empty (); }

private:
    std::vector <T> h_;
};

//如果需要针对 const char* 写显式特化,则所有接口都得重写一遍
//为此可只针对部分成员函数进行特化
template<>
void Heap2<const char *>::push (const char *const &pVal) //这里要求接口精确匹配
{
    std::cout << "Heap2<const char *>::push" << endl;
}

///
//总结1:对前面的各种模板,匹配原则是优先匹配最精确的。
///
//例如测试:
void test_template1 ()
{
    int x = 10;
    char s[] = "abc";

    Heap<int>           h1;
    Heap<int*>          h2;
    Heap<const int*>    h3;
    Heap<char*>         h4;
    Heap<const char*>   h5;

    h1.push (x);
    h2.push (&x);
    h3.push (&x);
    h4.push (s);
    h5.push (s);

    Heap2<int>           h21;
    Heap2<int*>          h22;
    Heap2<const int*>    h23;
    Heap2<char*>         h24;
    Heap2<const char*>   h25;

    h21.push (x);
    h22.push (&x);
    h23.push (&x);
    h24.push (s);
    h25.push (s);
}

///
//5.成员模板
///
template <typename T>
class SList
{
public:
    template <typename In> void Insert (In begin, In end) {} //这是一个成员模板
};

void SList_Test ()
{
    SList<double> list1;
    int datas[] = {1, 2, 3, 4};
    list1.Insert (datas, datas + sizeof (datas) / sizeof (datas[0]));
}

//注: 任何非虚函数都可以写出模板

///
//6.模板的模板参数
///
//假设需要实现一个 Stack 模板,这里默认用了 deque 作为底层容器
template <typename T>
class Stack
{
public:
    //...
private:
    std::deque <T> s_;
};

//现在希望能够灵活点,让用户可以配置底层容器
template <typename T, class Cont>
class Stack1
{
public:
    //...
private:
    Cont s_;
};

//Stack1 会有个小问题了
//例如可能会被这样使用
//Stack <double, std::list<int>> stack1;
//这里的 double 和 list 内部的 int 不匹配了,编译能通过,但可能会引起 bug
//为了安全,这样实现
template <typename T, template <typename, class> class Cont = std::list> //这里有模板参数了
class Stack2
{
    public:
    //...
private:
    Cont<T, std::allocator<T>> s_;
};

//好了,不用担心不匹配了
void Stack_test ()
{
    Stack1 <double, std::list<int>> stack1; //不安全
    Stack2 <double> stack2; //OK
    Stack2 <int, std::vector> stack3; //OK
}


///
//7.泛型编程1 嵌入类型信息
//使用 typename 消除歧义,指示一个 类型名
//使用 template 消除歧义,指示一个 模板名
///

//泛型函数,其中typename 指示后面的 ElemT 是一个类型名
//这里的 ElemT 就是一个嵌入类型信息
template <class Cont>
void myfill (Cont &c, typename Cont::ElemT a[], int len)
{
    for (int i = 0; i < len; ++i)
    {
        c.insert (a[i]);
    }
}

//这样 myfill 支持任意类型的 Cont,只要它内部定义了 ElemT 和 insert。
//例如
template <typename T>
class MyList
{
public:
    //...
    typedef T ElemT;    //嵌入类型信息
    void insert (const ElemT&) {}
    //...
};

//我们就可以这样使用它
void fill_test ()
{
    MyList<int> list;
    int datas[] = {1, 2, 3, 4};
    myfill (list, datas, sizeof(datas) / sizeof(datas[0]));
}

//使用 template 消除歧义
//我们可能参考 STL 定义一个内存配置器
template <class T>
class MyAlloc
{
public:
    //...
    template <class Other>
    class rebind
    {
    public:
        typedef MyAlloc<Other> other;
    };
};

//这样当我们设计一个容器时,可以传入配置器
//在这里 A 时配置 T 的,但实际在 MyList2 中还需要配置 _Node
//所以我们需要得到 A 的配置 _Node 的配置器
template <typename T, class A = MyAlloc<T>>
class MyList2
{
    struct _Node
    {
        _Node *next;
        T data;
    };

    //这里 template 提示后面的 rebind 是一个模板
    //typename 提示最终的 other 是一个类型
    typedef typename A::template rebind<_Node>::other NodeAlloc;
};

///
//8.泛型编程2 traits
///

//根据前面知识,实现一个泛型算法
template <class Cont>
typename Cont::ElemT Sum (const Cont &c, int size)
{
    typename Cont::ElemT ret = typename Cont::ElemT();
    for (int i=0; i<size; ++i)
    {
        ret += c[i];
    }
    return ret;
}

//这样任意类型的 Cont,只要它内部定义了 ElemT 和 [] 就可以使用 Sum。
//但如何让 Sum 应用更广泛,例如支持没有定义 ElemT 和 [] 容易,甚至支持类似容器的东西
//可以通过 traits 类实现
template <typename Cont>
struct ContTraits   //支持一般容器
{
    typedef typename Cont::ElemT ElemT;
};

template <int N>
struct ContTraits <int[N]> //支持 int[]
{
    typedef int ElemT;
};

template <class T>
struct ContTraits <std::vector<T>> //支持 std::vector
{
    typedef typename std::vector<T>::value_type ElemT;
};

//当然需要修改 Sum 了
template <class Cont>
typename ContTraits<Cont>::ElemT Sum2 (const Cont &c, int size)
{
    typename ContTraits<Cont>::ElemT ret = typename ContTraits<Cont>::ElemT();
    for (int i=0; i<size; ++i)
    {
        ret += c[i];
    }
    return ret;
}

//测试一下
void sum2_test ()
{
    const int size = 4;
    int datas[size] = {1, 2, 3, 4};
    vector<int> v (datas, datas + size);

    int ret1 = Sum2 (datas, size);
    int ret2 = Sum2 (v, size);
}

///
//9.泛型编程3 STL泛型编程惯例
//接口使用一对迭代器
//使用 swap 进行交换
//对于比较,除了默认的 < 操作符,还可以提供一个 STL函数对象
//...
///

//上面的 Sum2 可以修改为
template <class InIt, class OutElemT>
OutElemT Sum3 (InIt _First, InIt _Last)
{
    OutElemT ret = OutElemT();
    for (; _First < _Last; ++_First)
    {
        ret += *_First;
    }
    return ret;
}

void sum3_test ()
{
    const int size = 4;
    int datas[size] = {1, 2, 3, 4};
    vector<int> v (datas, datas + size);

    int ret1 = Sum3<int*, int> (datas, datas + size);
    int ret2 = Sum3<vector<int>::iterator, int> (v.begin (), v.end ());
}

///
//10.针对类型信息的特化
//从一个特化版本中推导出类型属性
///
//判断一个类型是否是指针
struct ISPTR_YES {};
struct ISPTR_NO {};
template <typename T>
struct IsPtr {
    enum { result = false };
    typedef ISPTR_NO Result;
};
template <typename T>
struct IsPtr <T *> {
    enum { result = true };
    typedef ISPTR_YES Result;
};
template <typename T>
struct IsPtr <T * const> {
    enum { result = true };
    typedef ISPTR_YES Result;
};

//针对这些信息,可以实现一个 clear_vector 函数
//当 std::vector 内部元素为指针时, clear_vector 会自动将内部的指针元素清空
template <typename T>
void _init_clear_vector_data (std::vector <T> &v, ISPTR_YES)
{
    for (std::vector <T>::iterator it = v.begin (); it != v.end (); ++it)
    {
        delete *it;
        *it = NULL;
    }
}

template <typename T>
void _init_clear_vector_data (std::vector <T> &v, ISPTR_NO)
{
}

template <typename T>
void clear_vector (std::vector <T> &v)
{
    _init_clear_vector_data (v, IsPtr<T>::Result ());
    v.clear ();
}

//测试
void clear_vector_test ()
{
    std::vector<int> v1;
    v1.push_back (1);
    v1.push_back (2);
    v1.push_back (3);
    clear_vector (v1);

    std::vector<int*> v2;
    v2.push_back (new int (1));
    v2.push_back (new int (2));
    v2.push_back (new int (3));
    clear_vector (v2);
}

///
//11.policy
///
//在10中,我们简单判断 std::vector 中元素为指针的话就 delete 掉
//但这样不够灵活,比如有时指针指向数组,或有时也不期望删除指针
//总之,我们期望能对删除操作进行配置,执行不同的 delete policy (删除策略)
template <typename T, template <typename> class DeletionPolicy>
void clear_vector2 (std::vector <T> &v)
{
    for (std::vector <T>::iterator it = v.begin (); it != v.end (); ++it)
    {
        DeletionPolicy<T> ()(*it);
    }
    v.clear ();
}

//然后可以定义不同的删除策略
template <typename T>
struct NoDeletePolicy {
    void operator () (T ptr) {
    }
};

template <typename T>
struct PtrDeletePolicy {
    void operator () (T ptr) {
        delete ptr;
    }
};

template <typename T>
struct ArrayDeletePolicy {
    void operator () (T ptr) {
        delete[] ptr;
    }
};

//定义无 policy 时的行为
template <typename T>
void clear_vector2 (std::vector <T> &v)
{
    v.clear ();
}

//现在可以这样使用
//测试
void clear_vector_test2 ()
{
    std::vector<int> v1;
    v1.push_back (1);
    v1.push_back (2);
    v1.push_back (3);
    clear_vector2<int> (v1);

    std::vector<int*> v2;
    v2.push_back (new int (1));
    v2.push_back (new int (2));
    v2.push_back (new int (3));
    clear_vector2<int*, PtrDeletePolicy> (v2);
}

///
//12.模板实参推导
///
//假设有个模板函数
template <typename T>
T my_min (const T &a, const T &b)
{
    return a < b ? a : b;
}

//可以这样用它
//int a = my_min (11, 12);
//不用特意指明类型,编译器会自动推导
//但这样就不行了: double d = my_min (11, 12.0);
//因为编译器无法确定类型是 int 还是 double
//必须这样用 double d = my_min<double> (11, 12.0);

//如果编译器可以自动推导出参数,则模板实参列表中靠后的模板实参可以省略
template <int n, typename T>
void repeat1 (const T &msg)
{
    for (int i=0; i<n; ++i)
        std::cout << msg << std::endl;
}
//可以这样用它 repeat1<10> ("abc");

//但如果 repeat 这样实现,那就麻烦了
template <typename T, int n>
void repeat2 (const T &msg)
{
    for (int i=0; i<n; ++i)
        std::cout << msg << std::endl;
}
//现在必须这样用了 repeat2<char*, 10> ("abc");

void template_test_12 ()
{
    int a = my_min (11, 12);
    double d = my_min<double> (11, 12.0);

    repeat1<10> ("abc");
    repeat2<const char*, 10> ("abc");
}

///
//13.测试代码
///
void template_test_all ()
{
    test_template1 ();
    Stack_test ();
    fill_test ();

    MyList2<int> list2;

    sum2_test ();
    clear_vector_test ();
    clear_vector_test2 ();

    template_test_12 ();
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值