• c++模板函数


    模板特化(也有翻译为模板具体化)(specialization)

      如果把模板函数当作数学归纳法的话,模板特化就是n=常数C的情况。

    //模板函数声明
    template <typename T>
    bool greater(const T &a,const T &b);
    int main()
    {
        return 0;
    }
    //模板函数定义
    template <typename T>
    bool greater(const T &a,const T &b)
    {
        if(a > b)
            return true;
        else
            return false;
    }
    

      如果上面的T是char*类型,那么这种比较是不符合我们要求的,它比较的是地址,char*应该用strcmp。

      如果按照我们最简单的思路的话就是直接将T换成char*,但这样编译却给出了error,说这个特化找不到对应的模板函数。

    #include <cstring>
    //模板函数声明
    template <typename T>
    bool greater(const T &a,const T &b);
    //特化的模板函数的声明
    template<>
    bool greater<char*>(const char * &a,const char * &b);
    int main()
    {
        return 0;
    }
    //模板函数定义
    template <typename T>
    bool greater(const T &a,const T &b)
    {
        if(a > b)
            return true;
        else
            return false;
    }
    //特化的模板函数的定义
    template<>
    bool greater<char *>(const char * &a,const char * &b)
    {
        return strcmp(a,b)>0?true:false;
    }
    

      我们模板函数的定义const T &a,其实也可以是这种写法T const &a,这两种写法表明const和T分别修饰a,也就是a的类型既是const又是T;而不是const T作为一个整体,a的类型是const T。虽然在这个问题上,这两种理解没有出现语义的误解,但是在上面的例子就出现误解了。

    根据我们原来的模板函数的定义,我们的目的应该是这样的特化函数的参数,`const (char *) &a`(是没有这种写法,只是为了让我们理解方便),也就是说char  *是一个整体,和const一起修饰a。`const char* &a`却让`const char`作为了一个类型,这显然不是我们要的。`char* const &a`这才是我们想要的。
    
    #include <cstring>
    //模板函数声明
    template <typename T>
    bool greater(T const &a,T const &b);
    //特化的模板函数的声明
    template<>
    bool greater<char*>(char * const &a,char * const &b);
    int main()
    {
        return 0;
    }
    //模板函数定义
    template <typename T>
    bool greater(T const &a,T const &b)
    {
        if(a > b)
            return true;
        else
            return false;
    }
    //特化的模板函数的定义
    template<>
    bool greater<char*>(char * const &a,char * const &b)
    {
        return strcmp(a,b)>0?true:false;
    }
    

      如果在写类型时,习惯将const放在类型char等后面,那么写模板特化时,直接代换T就不会出现错误。

    定义与实现分离?

      在传统上,我们总是把定义写在.h文件里,而实现文件写在.cpp文件里,但这在模板里面是否可以,请看下面一个例子。

    //swap.h
    template <typename T>
    void swap(T &a,T &b);
    //swap.cpp
    template <typename T>
    void swap(T &a,T &b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    //main.cpp
    #include "swap.h"
    int main()
    {
        int a,b;
        swap(a,b);
        //...
        return 0;
    }
    

      编译器在编译main.cpp文件时只看到swap的声明而没有定义(实现),所以留空,让连接器去链接swap的实现。而编译器在编译swap.cpp时没有看到模板参数,所以也不会编译,最后便会导致链接错误,即找不到swap<int>。

      所以模板函数或者模板类,应该将定义和实现放在.h文件。

    显示实例化(explict initialization)

      实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程。
      隐式实例化:这是编译器看到模板函数时,在当前文件实现相应的模板参数的实例化。
      显示实例化:就是自己手工让编译器在此文件实现相应的模板参数的实例化。
    既然编译器会自动实现实例化,为什么还要我们去手工去让编译器实现实例化呢?请看下面的例子。

    //swap.h
    template <typename T>
    void swap(T &a,T &b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    //function.h
    void function();
    //function.cpp
    #include "swap.h"
    void function()
    {
    	int a,b;
    	swap(a,b);
    }
    //main.cpp
    #include "swap.h"
    #include "function.h"
    int main()
    {
        int a,b;
        swap(a,b);
    	function();
        //...
        return 0;
    }
    

      为了容易看出,我们暂且用function表示一个用到swap的函数,它也可以是一个类。
      编译器在编译function.cpp时会用void swap<int>(int &a,int &b),故生成的obj文件有void swap<int>(int &a,int &b)的二进制代码,同理,在main.cpp生成的obj文件里也有void swap<int>(int &a,int &b)的二进制代码。也就是说最后生成的exe有两个相同的二进制代码。
      为了减少代码,我们可以用一个cpp文件(就起名为explict_initilization.cpp)手动显示实例化这些模板类,如下。这样的话,链接器最后会将main.cpp和function.cpp中的swap<int>链接到explict_initilization.cpp。

    //swap.h
    template <typename T>
    void swap(T &a,T &b);
    //swap
    templatee <typename T>
    void swap(T &a,T &b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    //explict_initilization.cpp
    #include "swap.cpp"
    template void swap<int>(int&,int&);//显示实例化
    //function.h
    void function();
    //function.cpp
    #include "swap.h"
    void function()
    {
    	int a,b;
    	swap(a,b);
    }
    //main.cpp
    #include "swap.h"
    #include "function.h"
    int main()
    {
        int a,b;
        swap(a,b);
    	function();
        //...
        return 0;
    }
    

    更加详细的介绍可以看c++模板类(一)理解编译器的编译模板过程

    export的用法

      为了解决上面用一个explict_initilization.cpp来管理实例化文件的不便利,c++可以export这个关键词,让编译器和链接器去管理我们上面要解决的事情。

    //swap.h
    export
    template <typename T>
    void swap(T &a,T &b);
    //swap
    templatee <typename T>
    void swap(T &a,T &b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    //function.h
    void function();
    //function.cpp
    #include "swap.h"
    void function()
    {
    	int a,b;
    	swap(a,b);
    }
    //main.cpp
    #include "swap.h"
    #include "function.h"
    int main()
    {
        int a,b;
        swap(a,b);
    	function();
        //...
        return 0;
    }
    

    通过export这个关键字,即使在模板定义不可见的条件下,被导出的模板也可以正常使用,当编译器看到export这个关键字,会自动去搜寻模板的定义。
    这个看似很美好,但是在export这个关键字推出到2011年,很少有编译器去实现这个关键字的功能,所以在C++11到C++20这段期间被作为了一个保留字,C++20又被赋予了新的意义。

  • 相关阅读:
    windows测试模式
    架构设计之Spring-Session的分布式集群会话管理
    WPF集合
    java Socket Udp
    java Socket 获取本地主机ip
    快速排序
    java 正则举例
    JNI 在命令行窗口输入字符,不显所输入字符,显指定的掩饰符
    java 在控制台上输入密码时,密码不显示在控制台上
    java 获取对象大小
  • 原文地址:https://www.cnblogs.com/h-hg/p/8729098.html
Copyright © 2020-2023  润新知