zoukankan      html  css  js  c++  java
  • C++模板编译模型

    一:传统的编译模型

             使用C/C++进行编程时,一般会使用头文件以使定义和声明分离,并使得程序以模块方式组织。将函数声明、类的定义放在头文件中,而将函数实现以及类成员函数的定义放在独立的文件中。

             但是对于模板来说,这种方式是行不通的,具体的例子如下:

             首先是包含模板声明的头文件temp.h:

    //temp.h
    
    #ifndef TEMP_H
    #define TEMP_H
    
    template<typename T>
    int compare(const T &a, const T &b);
    
    template<typename T>
    class testtemp
    {
    public:
        testtemp(const T &a):m_value(a){}
        void display();
    
    private:
        T   m_value;
    };
    
    #endif
    

        该头文件中包含了一个函数模板的声明,以及一个类模板的定义。

             下面是包含模板定义的源码文件temp.cpp:

    //temp.cpp
    
    #include <iostream>
    #include "temp.h"
    
    template<typename T>
    int compare(const T &a, const T &b)
    {
        if (a < b)  return -1;
        if (b < a)  return 1;
        return 0;
    }
    
    template <typename T> 
    void testtemp<T>::display()
    {
        std::cout << m_value << std::endl;
    }

             下面是主函数文件main.cpp:

    //main.cpp
    
    #include <iostream>
    #include "temp.h"
    
    int main()
    {
        int a = 1, b = 3;
        int res;
    
        testtemp<int> tt(4);
        tt.display();
    
        res = compare(a, b);
        std::cout << "res is " << res << std::endl;
    }
    

      

             对上面的文件编译生成可执行文件时,会报错:

    # g++ -o main main.cpp temp.cpp
    
    /tmp/ccNwfO8x.o: In function `main':
    
    main.cpp:(.text+0x47): undefined reference to `testtemp<int>::display()'
    
    main.cpp:(.text+0x5a): undefined reference to `int compare<int>(int const&, int const&)'
    
    collect2: error: ld returned 1 exit status
    

       

             报错的原因如下:

             C++中每一个对象所占用的空间大小,都是在编译的时候就确定的。在编译阶段,源码文件main.cpp将包含模板声明的头文件temp.h包含进来之后,编译器就需要为main.cpp中涉及到的每个对象生成合适的内存布局,为每个函数生成相应的指令。

             当源码文件main.cpp中涉及到模板类成员函数或者模板函数的调用时,因为模板函数的定义在另一个源码文件temp.cpp中,编译器目前仅仅知道它们的声明。所以,在main.cpp中调用到的的testtemp<int>::display函数,以及int compare<int>(int const&, int const&)函数,编译器认为这些函数的实现是在其他源码文件中的,编译器不会报错,因为连接器会最终将所有的二进制文件进行连接,从而完成符号查找,形成一个可执行文件。

             尽管编译器也编译了包含模板定义的源码文件temp.cpp,但是该文件仅仅是模板的定义,而并没有真正的实例化出具体的函数来。因此在链接阶段,编译器进行符号查找时,发现源码文件中的符号,在所有二进制文件中都找不到相关的定义,因此就报错了。

     

    二:模板的编译模型

             当编译器看到模板定义的时候,它不立即产生代码。只有在看到用到模板时,如调用了函数模板或调用了类模板的对象的时候,编译器才产生特定类型的模板实例。

             一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。

             模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码。

     

             标准 C++ 为编译模板代码定义了两种模型。分别是包含编译模型和分别编译模型。

             所谓包含编译模型,说白了,就是将函数模板的定义放在头文件中。因此,对于上面的例子,就是将temp.cpp的内容都放到temp.h中。

             包含编译模型有个问题,如果两个或多个单独编译的源文件使用同一模板,这些编译器将为每个文件中的模板产生一个实例。因此给定模板会产生多个相同的实例,在链接的时候,编译器会选择一个实例化而丢弃其他的。

     

             在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,因此需要使用 export 关键字。但是,实际上很多编译器都不支持这个关键字,而且C++11 将这个关键字设置为 unsued 和 reserved 了。

     

             所以,结论就是,把模板的定义和实现都放到头文件中。

     

    参考:

    http://gaunthan.leanote.com/post/C-%E6%A8%A1%E6%9D%BF%E7%9A%84%E7%BC%96%E8%AF%91%E6%A8%A1%E5%9E%8B

    https://www.zhihu.com/question/20630104

  • 相关阅读:
    自然数幂和的若干种解法
    线性预处理逆元
    差分与有限微积分
    UVALive 6859——凸包&&周长
    UVALive 6858——分类讨论&&水题
    UVALive 6862——结论题&&水题
    ZOJ4019——贪心&&DP
    [LeetCode] Power of Two
    循环队列实现(C++) Ring Buffer
    正确使用stl vecotr erase函数
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7181400.html
Copyright © 2011-2022 走看看