最近想封装一个可以传入任意枚举类型的接口,于是研究了下C++中模版和模版函数,此处只简单介绍一下模版函数:
在 C++ 中,模板函数在类内部和类外部定义时的行为是不同的。模板函数在类内部定义时会隐式实例化,而在类外部定义时需要显式实例化,原因与 C++ 编译过程和模板的实例化机制密切相关。
1. 模板函数在类内部定义时
当你在类的内部定义一个模板函数时,编译器会在编译过程中自动实例化该模板函数,针对你在代码中使用到的每一种类型实例化该模板。这是因为模板函数的定义是包含在类的声明中的,因此类的每个实例化都会伴随自动生成该模板函数的实例。
示例 1:模板函数写在类内部
template <typename T>
class MyClass {
public:
void foo(T arg) {
// 模板函数的实现
std::cout << arg << std::endl;
}
};
这里 foo
是一个模板函数,定义在 MyClass
类的内部。当你在代码中创建 MyClass<int>
或 MyClass<double>
的实例时,编译器会自动根据传入的类型生成 foo
函数的实例:
MyClass<int> obj1;
obj1.foo(42); // 自动实例化 foo<int>
MyClass<double> obj2;
obj2.foo(3.14); // 自动实例化 foo<double>
这种情况下,编译器会根据你创建的 MyClass
的类型来自动实例化 foo
函数。你不需要显式实例化模板,编译器会根据需要生成相应的代码。
2. 模板函数在类外部定义时
如果模板函数在类外部定义(即在类外部的实现文件中),编译器无法像在类内部定义时那样自动实例化模板函数。编译器需要知道具体使用的模板类型,才能生成相应的函数实例。因此,必须显式实例化模板函数,告诉编译器为哪些类型生成代码。
示例 2:模板函数写在类外部
template <typename T>
class MyClass {
public:
void foo(T arg); // 只声明模板函数
};
// 在类外部定义模板函数
template <typename T>
void MyClass<T>::foo(T arg) {
std::cout << arg << std::endl;
}
在上面的代码中,foo
函数在类的外部进行了定义,但没有显式实例化。当你使用 MyClass<int>
或 MyClass<double>
时,编译器不会自动实例化 foo
函数,因为它只能看到模板声明,而不能直接知道需要哪些类型的实例化。
为了显式实例化模板函数,你必须告诉编译器生成哪些类型的函数实例。这就是显式实例化的用法。
显式实例化示例:
template class MyClass<int>; // 显式实例化 MyClass<int>
template class MyClass<double>; // 显式实例化 MyClass<double>
这告诉编译器生成 MyClass<int>
和 MyClass<double>
类型的实例,并为这些类型自动生成 foo
函数的实例。
如果你没有显式实例化模板函数,那么编译器会尝试在你使用该函数时进行实例化。如果你没有提供明确的实例化声明,编译器就会报错,提示找不到模板的实例。
为什么需要显式实例化
模板函数在类外部定义时,编译器并不能在类实例化时自动生成所有可能的模板实例。为了使编译器能够生成代码,你需要告诉它确切的类型,这就需要显式实例化模板。
显式实例化的作用
显式实例化的主要作用是:
- 减少代码膨胀:在大型程序中,模板类和模板函数可能会生成大量的实例化代码。通过显式实例化,你可以控制哪些类型的实例化被生成,而避免不必要的实例化。
- 减少编译时间:在某些情况下,显式实例化可以减少编译时间,因为编译器不必在每个翻译单元中都进行实例化操作。
- 管理模板实例化:显式实例化可以帮助你在编译器无法推导出模板类型时明确告诉编译器需要哪些类型的实例。
总结
- 模板函数在类内部定义时:编译器会自动实例化模板函数,根据你实际使用的类型进行实例化。你不需要显式实例化模板。
- 模板函数在类外部定义时:编译器不能自动实例化模板函数,需要显式实例化,告诉编译器为哪些类型生成函数实例。
- 在C++中模版函数要调用普通函数: 如果模版函数后面有const,那么普通函数必须使用const
模板函数在类外部定义时,需要显式实例化是因为编译器无法在类外部自动推导出类型,所以你需要显式告诉编译器哪些类型的实例化是必要的。