走过的路

记录自己曾经认真思考过的问题、做过的事情

C++的模板的声明和定义

在Nvidia面试的的时候,被问到这么一个问题:

我们经常会这样写代码:把类的声明写在.h的头文件中,把定义写在.cpp中,这样包含头文件就可以使用这个类和他的方法了。对于模板的话,如果也这么写,会不会有什么问题呢?如果有问题,又是为什么呢?

从网上copy过来一段模板的代码:

// array.h        
template <typename T, int SIZE>
class array
{
      T data_[SIZE];
      array (const array& other);
      const array& operator = (const array& other);
public:
      array(){};
      T& operator[](int i);
      const T& get_elem (int i) const;
      void set_elem(int i, const T& value);
      operator T*();      
};        
    
// array.cpp
#include "array.h"

template<typename T, int SIZE> T& array<T, SIZE>::operator [](int i)
    {
    return data_[i];
    }

template<typename T, int SIZE> const T& array<T, SIZE>::get_elem(int i) const
    {
    return data_[i];
    }

template<typename T, int SIZE> void array<T, SIZE>::set_elem(int i, const T& value)
    {
    data_[i] = value;
    }
template<typename T, int SIZE> array<T, SIZE>::operator T*()
    {
    return data_;
    }

然后在main中调用模板:

#include "array.h"

int main(void)
{
array<int, 50> intArray;
intArray.set_elem(0, 2);
int firstElem = intArray.get_elem(0);
int* begin = intArray;
}

这段代码在链接的时候会有三个错误,为什么呢?有四个函数,为什么只有三个链接错误呢?

分两步来解释原因:

1,非模板的头文件和cpp文件是怎么编译的?

如果不是模板,编译器做的事情是,在编译阶段,编译每一个cpp文件,检查头文件中有没有被调用函数的声明,如果有,编译通过,进入连接阶段,这一步为每一个函数声明在对应的cpp文件中寻找函数的定义,如果找到,则继续下一步,否则显示链接错误;


2,模板又是怎么编译的?

C++的模板概念和作用就不解释了,这个是基础。模板需要对每一个类型进行实例化,生成对应的源文件,并且C++模板的实例化是,遇到了对应的调用,才会产生相应的实例,意思是说,上面的代码中,在main函数中只调用了set_elem, get_elem和operator *这三个函数,那么只有这三个函数被实例化了,operator[]没有被调用过,便没有实例化它。

所以编译器在编译的时候会根据调用实例化头文件中的模板,生成对应的声明,但是不会去实例化cpp中的定义,因为使用定义本身就不是在编译的时候做的,而是链接的时候再去找定义的。所以编译会通过,但是链接的时候会找不到声明对应的定义,因为在编译的时候没有对定义实例化,而实例化又不是链接部分的职责。于是出现了链接错误。

由于operator[]没有被调用,不会有这个函数的实例化,自然也就不会有链接错误。


至于解决办法,一般是把模板的定义写到头文件中,几乎所有的编译器都支持这种方式;

另一种方法是,在cpp文件的每个函数前增加export声明,告诉编译器这些是对应于声明的函数,在编译时进行实例化。这个方法有的编译器支持,有的不支持,VS就不支持这种方式。


阅读更多
文章标签: c++ 编译器 面试 c
个人分类: C++ 模板
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭