系列文章目录
- C++11 29-外部模板 Extern Template
Overview
1.外部模板 Extern Template
在 C++11 中,外部模板(Extern Template)是一种用来优化编译时间和减少编译冗余的特性。它允许开发者告诉编译器,某个模板的实例化应该在特定的编译单元中进行,避免在每个包含模板定义的编译单元中都进行实例化,从而减少编译时间并降低链接时的冗余。
1.1.如何使用外部模板
-
在头文件中声明模板:
首先,在头文件中声明模板,但不进行定义。// example.h template <typename T> class Example { public: void function() { /* ... */ } };- 1
- 2
- 3
- 4
- 5
- 6
-
在源文件中定义模板,并使用
extern关键字声明:
在一个源文件中定义模板,并使用extern关键字声明,这样编译器就知道这个模板的实例化应该在此处进行。// example.cpp #include "example.h" template class Example<int>; // 显式实例化 extern template class Example<int>; // 外部模板声明- 1
- 2
- 3
- 4
- 5
-
在其他源文件中使用外部模板声明:
在其他需要使用该模板的源文件中,使用extern关键字来声明模板,这样编译器就不会再次实例化该模板。// another_file.cpp #include "example.h" extern template class Example<int>; // 外部模板声明- 1
- 2
- 3
- 4
1.2.注意事项
- 外部模板声明不会阻止模板的隐式实例化,如果其他编译单元中使用了模板并且没有看到外部模板声明,编译器仍然会为它生成实例化代码。
- 外部模板通常与模板的显式实例化一起使用,以确保一致性和避免重复实例化。
- 如果外部模板声明出现在某个编译单元中,那么与之对应的显式实例化必须出现在另一个编译单元中或者同一个编译单元的后续代码中。
- 外部模板声明不能用于一个静态函数,但可以用于类静态成员函数。
使用外部模板可以显著优化编译时间,提高模板使用的灵活性和可控性,并增强代码的可移植性,这对于大型 C++ 项目的开发非常有帮助。
2.C++11 中的外部模板和内部模板有什么区别?
在 C++ 中,模板的“内部”和“外部”之分主要关联它们的定义和声明的位置,以及它们如何影响编译器的实例化过程。理解这一区分对于优化大型项目中的编译时间非常关键。
2.1.内部模板(Inline Templates)
-
定义:
- 内部模板是指在头文件中定义的模板。当头文件被多个源文件包含时,模板代码在每个源文件中都会有一份拷贝。
-
编译过程:
- 每当模板函数被调用时,编译器会为每个特定的模板参数实例化该模板。
- 如果模板定义在头文件中,那么每个包含该头文件的源文件都会生成模板的实例化代码,这可能导致编译时间增长和二进制大小增加。
-
优点:
- 由于模板的代码直接可见,编译器可以方便地在需要时进行实例化,不需要额外的链接步骤。
-
缺点:
- 多次包含同一个头文件会导致模板代码的多份拷贝,增加编译负担。
- 可能导致编译过程中的冗余工作,尤其是在大型项目中。
2.2.外部模板(Extern Templates)
-
定义:
- 外部模板是指在某个源文件中明确实例化的模板,而其定义保留在头文件中。
-
编译过程:
- 通过在源文件中使用
extern template声明,可以告诉编译器该模板的实例化应该限制在特定的编译单元。 - 其他源文件中通过
extern template引用该模板时,编译器会知道跳过实例化过程,避免重复实例化。
- 通过在源文件中使用
-
优点:
- 减少编译时间,因为每个模板只实例化一次。
- 减少二进制文件的大小,因为不会在每个编译单元中重复实例化。
-
缺点:
- 需要显式管理模板的实例化位置,增加了使用复杂度。
- 如果实例化不正确,可能导致链接错误。
2.3.示例说明
内部模板:
// MyTemplate.h
template <typename T>
class MyTemplate {
public:
void function() { }
};
// MyTemplate.cpp 或其他 cpp 文件
#include "MyTemplate.h"
template class MyTemplate<int>; // 显式实例化
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
外部模板:
// MyTemplate.h
template <typename T>
class MyTemplate {
public:
void function() { }
};
// MyTemplate.cpp
#include "MyTemplate.h"
extern template class MyTemplate<int>; // 外部声明
template class MyTemplate<int>; // 实际实例化
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
// OtherFile.cpp
#include "MyTemplate.h"
extern template class MyTemplate<int>; // 引用外部声明
- 1
- 2
- 3
2.4.总结
内部模板和外部模板的主要区别在于它们如何被实例化和管理。内部模板提供了灵活性但可能导致编译效率问题,而外部模板有助于优化编译过程,特别是在大型项目中。正确使用外部模板可以显著提高编译效率和减少二进制大小。
3.如何使用外部模板来优化大型项目的编译时间?
使用外部模板(Extern Templates)可以显著减少大型项目的编译时间,因为它们可以避免模板类或函数的多次实例化。以下是使用外部模板来优化大型项目编译时间的步骤:
3.1. 将模板定义放在头文件中
首先,将模板类或函数的定义放在一个头文件中,并确保不在头文件中进行任何模板的显式实例化。
// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
template <typename T>
class Example {
public:
void function() {
// ...
}
};
#endif // EXAMPLE_H
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3.2. 在一个源文件中进行显式实例化
选择一个源文件进行模板的显式实例化,并在该源文件的顶部声明 extern template。
// example.cpp
#include "example.h"
// 显式实例化 Example
template class Example<int>;
// 声明其他可能的实例化,以防止在其他源文件中重复实例化
extern template class Example<float>;
extern template class Example<double>;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.3. 在其他源文件引用外部模板
在其他源文件中引用模板类或函数时,使用 extern template 声明来告诉编译器该模板的实例化应该在其他地方进行。
// another_file.cpp
#include "example.h"
extern template class Example<int>;
extern template class Example<float>;
extern template class Example<double>;
// ... 使用 Example 类 ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3.4. 确保包含头文件的顺序正确
确保在包含模板头文件之前声明外部模板,这样编译器在处理到模板定义之前已经知道这是一个外部模板。
3.5. 避免在头文件中进行实例化
避免在头文件中进行模板的显式实例化,因为这会导致每次包含头文件时都进行实例化。
3.6. 为常用类型创建专有的实例化文件
对于大型项目中常用的模板类型,可以创建一个专用的实例化文件,集中管理所有的实例化。
3.7. 使用编译器的优化选项
使用编译器的优化选项,如 GCC 和 Clang 的 -flto(Link Time Optimization),可以进一步减少重复代码。
3.8. 分析编译过程
使用编译器的分析工具,如 -ftime-report 或者profiling tools,来分析编译过程,找出哪些模板的实例化占用了最多的编译时间,并针对性地进行优化。
3.9. 控制模板的泛化
限制模板的泛化程度,避免创建过于通用的模板,因为它们更可能被多次实例化。
3.10. 教育团队成员
确保团队中的所有成员都了解外部模板的使用方式,以及它们如何帮助减少编译时间。
通过以上步骤,你可以有效地使用外部模板来优化大型项目的编译时间。记住,关键在于集中管理模板的实例化,并确保每个模板的实例化只发生一次。
4.在C++11中,除了外部模板,还有哪些其他特性可以减少编译时间?
在 C++11 中,除了外部模板,还有许多其他特性可以帮助减少编译时间:
-
自动类型推导 (
auto关键字):可以减少手动指定类型的需要,尤其是在处理复杂的模板或容器类型时。 -
基于范围的 for 循环:提供了一种更简洁和直观的方式来遍历容器或数组,减少了编写和维护循环代码的时间。
-
Lambda 表达式:允许在需要的地方定义匿名函数,这在与标准库算法一起使用时特别有用,并且可以减少编写和维护函数的时间。
-
并发编程支持:包括
std::thread,std::async等,使得编写多线程代码更加容易,并且可以利用多核处理器来加速程序的执行。 -
正则表达式支持 (
std::regex):提供了一种方便的方式来处理字符串匹配和操作,减少了处理字符串时的复杂性。 -
原子操作和线程局部存储:通过
std::atomic和thread_local关键字,提供了基本的同步操作,有助于编写更高效的并发代码。 -
可调用对象的包装器 (
std::function):可以存储、复制和调用任何可调用对象,包括函数指针、函数对象和 lambda 表达式,这有助于编写更灵活的代码。 -
类型安全的枚举 (
enum class):提高了枚举类型的安全性和清晰度,有助于减少类型转换错误。 -
委托构造函数:允许一个构造函数调用另一个构造函数,这有助于减少构造函数的重复代码。
-
final关键字:用于防止进一步继承类或重写虚函数,有助于防止错误的继承和重写。 -
override关键字:确保派生类正确地重写了基类的虚函数,提高代码的安全性和可维护性。 -
变长模板:允许创建接受任意数量模板参数的模板,有助于编写更通用的代码。
-
static_assert:提供了一种在编译时进行断言的方式,有助于提前发现潜在的问题。 -
decltype:用于推导表达式的类型,常用于模板元编程。 -
noexcept:用于指定函数不会抛出异常,有助于异常安全的编程。 -
完美转发(Perfect Forwarding):允许函数将其接收到的参数以完全不变的形式转发给另一个函数,有助于编写更高效的模板代码。
-
右值引用和移动语义:通过引入移动构造函数和移动赋值操作符,以及配合右值引用,为 C++ 提供了一种高效的资源管理方式,改善了程序的性能。
这些特性一起为 C++ 程序员提供了更多的工具来编写可扩展、可维护和现代化的代码。
关于作者
- 微信公众号:WeSiGJ
- GitHub:https://github.com/wesigj/cplusplusboys
- CSDN:https://blog.csdn.net/wesigj
- 微博:
- -版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
评论记录:
回复评论: