5

对于基于模板的类(STL 和 boost),不使用源文件并将实现也放入头文件似乎是一种常见的约定。我认为与头文件和源文件中声明和实现之间的经典分离相比,这将大大增加编译包含头文件的源文件所需的时间。这样做的原因可能是因为您必须在源文件中告诉编译器要使用哪些模板,这可能会导致 .a 文件膨胀。

假设随着库的增长,链接器也需要更多时间,那么就编译包含库头的源文件所需的时间而言,哪种方法会更快?

1.不使用.cpp文件,把整个类,包括实现,放到头文件中

//foo.hpp
template <class T>
class Foo
{
public:
    Foo(){};
    T bar()
    {
        T* t = NULL;
        //do stuff
        return *t;
    }
};

或者

2. 为库本身的源文件里面的各种类型显式编译模板

//foo.h
template <class T>
class Foo
{
public:
    Foo(){};
    T bar();
};

//foo.cpp
template <class T>
T Foo<T>::bar()
{
    T* t = NULL;
    //do stuff
    return *t;
}

template class Foo<int>;
template class Foo<float>;
template class Foo<double>;
template class Foo<long long>;
4

3 回答 3

4

通常,编译器为特定编译单元生成一次代码,然后链接器确保其他编译单元可以访问变量和函数。

当涉及到模板时,情况不再如此。在模板的具体实例被实例化之前,编译器无法为模板生成代码。因此,我们在每个编译单元中实例化模板,而无需复制和粘贴的唯一方法是将所有模板代码放在头文件中。

链接器还必须具有模板意识并协调同一对象的多个实例。

C++11 在这种情况下确实有点帮助,人们可以声明:

template class MyTemplate<MyType>;

在单个 C++ 文件中,然后使用:

extern template class MyTemplate<MyType>;

后者不会在当前编译单元中实例化模板,但会让链接器链接到已经定义的模板。

看到这里更多细节

于 2012-10-16T15:34:50.577 回答
4

模板的关键问题是编译器不知道模板将用于哪些模板参数。编译器知道模板与特定参数集一起使用的唯一时间是当它看到使用的模板并且编译器将在此时实例化模板。因此,代码通常被放入头文件中,因此编译器可以在使用时代表用户实例化模板。

或者,模板的作者可以告诉编译器模板与特定的模板参数列表一起使用,然后显式地实例化它们。在这种情况下,模板定义可以进入源文件(或者,更有可能是用户通常不包含的特殊标题)。这种方法的问题是模板代码的作者不一定知道需要哪些实例化。

在 C++ 2011 中还有一个中间立场:可以通过将特化声明为extern. 这样,编译器知道它不需要使用某些参数来实例化模板,但如果使用了其他参数,它就知道它需要创建它们。例如,标准 C++ 库具有std::basic_string并且它可以预测charwchar_t可能使用的实例化,并且可以将它们放入库中,将实例化声明为extern. 然而,让代码随时可用使得它可以std::basic_string<user_type>与用户定义的类型一起使用。

展望未来,我们希望得到一个模块系统,但现在没有人真正知道这样的系统应该如何真正工作。对该主题感兴趣的编译器实现者有一个小组来考虑模块,并且像这样的系统可能有助于模板的编译时间。

于 2012-10-16T15:48:47.587 回答
1

已经有两个很好的答案了,我就简单写一下。没有具体
类型 就无法编译模板代码,这就是为什么您不能进行经典的“编译和链接”。 模板函数或类是不完整的,因为并非所有类型都被解析,直到它在代码中的特定用法(某种类型替换模板)。 出于这个原因,模板放在头文件中,不能独立于特定用途进行编译。

于 2012-10-16T15:58:28.370 回答