面试题答案
一键面试参数类型推导
- 基本类型推导
- 当调用
func
函数时,编译器会根据传入的实参类型推导出模板参数T
的类型。例如:
#include <iostream> template <typename T> void func(const T& param) { std::cout << "T type deduced: "; typeid(T).print_name(); std::cout << std::endl; } int main() { int num = 10; func(num); return 0; }
- 在上述代码中,传入
int
类型的变量num
,编译器会推导出T
为int
,param
的类型为const int&
。
- 当调用
- 数组类型推导
- 如果传入数组,数组会退化为指针类型。例如:
#include <iostream> template <typename T> void func(const T& param) { std::cout << "T type deduced: "; typeid(T).print_name(); std::cout << std::endl; } int main() { int arr[5] = {1, 2, 3, 4, 5}; func(arr); return 0; }
- 这里编译器会推导出
T
为int*
,param
的类型为const int*&
。
- 函数类型推导
- 函数名可以作为函数指针传递,此时也会进行类型推导。例如:
#include <iostream> void testFunc() {} template <typename T> void func(const T& param) { std::cout << "T type deduced: "; typeid(T).print_name(); std::cout << std::endl; } int main() { func(testFunc); return 0; }
- 编译器会推导出
T
为void(*)()
,param
的类型为const void(*)&
。
生命周期管理
- 临时对象的生命周期延长
- 当传入临时对象作为实参时,其生命周期会延长到
func
函数调用结束。例如:
#include <iostream> #include <string> template <typename T> void func(const T& param) { std::cout << param << std::endl; } int main() { func(std::string("Hello")); return 0; }
- 这里
std::string("Hello")
是一个临时对象,由于param
是const T&
类型,它的生命周期会延长到func
函数结束,从而保证param
在函数内可以安全访问。
- 当传入临时对象作为实参时,其生命周期会延长到
函数重载解析
- 与非模板函数的重载
- 如果存在与模板函数
func
同名的非模板函数,编译器会优先匹配非模板函数。例如:
#include <iostream> void func(const int& param) { std::cout << "Non - template func called: " << param << std::endl; } template <typename T> void func(const T& param) { std::cout << "Template func called: "; typeid(T).print_name(); std::cout << std::endl; } int main() { int num = 10; func(num); return 0; }
- 这里会调用非模板函数
func(const int& param)
,因为非模板函数是一个更好的匹配。
- 如果存在与模板函数
潜在问题及解决
- 类型推导歧义
- 问题:当有多个模板函数存在,且它们的参数类型推导可能产生歧义时,编译会出错。例如:
template <typename T> void func(const T& param) { std::cout << "General template" << std::endl; } template <typename T> void func(const T* param) { std::cout << "Pointer template" << std::endl; } int main() { int num = 10; func(&num); // 编译错误,类型推导歧义 return 0; }
- 解决:明确指定模板参数或者修改模板函数使其更具特异性。例如:
template <typename T> void func(const T& param) { std::cout << "General template" << std::endl; } template <typename T> void func(const T* param, int = 0) { std::cout << "Pointer template" << std::endl; } int main() { int num = 10; func(&num); return 0; }
- 这里通过给第二个模板函数添加一个默认参数,使其在匹配
&num
时成为更好的选择。
- 对象切片问题
- 问题:如果传入派生类对象给
func
,并且func
中对param
进行操作时,可能会发生对象切片。例如:
class Base { public: virtual void print() { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void print() override { std::cout << "Derived" << std::endl; } }; template <typename T> void func(const T& param) { param.print(); } int main() { Derived d; func(d); return 0; }
- 如果
T
被推导为Derived
,由于param
是const T&
,param.print()
会调用Base
类的print
函数,发生对象切片。 - 解决:使用
const_cast
去除const
限定(需谨慎使用)或者在func
中使用static_cast
等进行动态类型转换(如果合适)。例如:
class Base { public: virtual void print() { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void print() override { std::cout << "Derived" << std::endl; } }; template <typename T> void func(const T& param) { const_cast<T&>(param).print(); } int main() { Derived d; func(d); return 0; }
- 这里通过
const_cast
去除param
的const
限定,使得print
函数可以正确调用Derived
类的版本。但要注意const_cast
的使用场景,避免引发未定义行为。
- 问题:如果传入派生类对象给
利用这些机制实现高效泛型编程
- 代码复用
- 模板函数
func
可以接受各种类型的参数,避免了为每种类型编写重复的函数。例如,对于不同类型的打印操作:
#include <iostream> #include <string> template <typename T> void func(const T& param) { std::cout << param << std::endl; } int main() { int num = 10; std::string str = "Hello"; func(num); func(str); return 0; }
- 这样可以复用
func
函数的代码逻辑,提高代码的可维护性和复用性。
- 模板函数
- 性能优化
- 由于使用
const T&
作为参数,避免了不必要的对象拷贝。对于大型对象,这可以显著提高性能。例如:
#include <iostream> #include <vector> template <typename T> void func(const T& param) { // 对 param 进行操作,这里不进行拷贝 std::cout << "Size of vector: " << param.size() << std::endl; } int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; func(vec); return 0; }
- 如果
func
的参数是T param
,则会对vec
进行拷贝,而使用const T&
避免了这种开销,实现了高效的泛型编程。
- 由于使用