【c++指南】模板VS手写代码:这场效率对决你站哪边?【下】
非类型模版参数
在 模版【上】章节中模版参数当作类型来处理,实际上模版参数还有非类型模版参数
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常
量来使用。
代码语言:javascript代码运行次数:0运行复制#define N 10
template<class T>
class myvector
{
public:
//...
private:
T _arr[N];
int _capacity;
};
int main()
{
myvector<int> v1;
myvector<double> v2;
return 0;
}
在上面这段程序当中,N的大小是确定的,即如果要插入100个数据时,N是10是不够的,需要手动将N的大小进行修改;当要插入10个数据时,N是100又太多了,导致很多空间被浪费。因此槽点很多,如果使用非类型模版参数可以改善这种问题。
代码语言:javascript代码运行次数:0运行复制template<class T,size_t N>
class myvector
{
public:
//...
private:
T _arr[N];
int _capacity;
};
此时N的大小我们可以自行决定,此时一个double类型的vector要开100个空间
代码语言:javascript代码运行次数:0运行复制myvector<double, 100> v;
int类型的vector要开10个空间
代码语言:javascript代码运行次数:0运行复制myvector<int, 10> v;
实际上,在C++库中的array(数组)也是这样处理的,那它又和int array[N]有什么区别呢?
C语言中的int array对越界问题检查实际是不严格的。
C++中array对越界问题的检查。
需要注意的是
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
特化
通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些
错误的结果 ,需要特殊处理。
代码语言:javascript代码运行次数:0运行复制template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl;
Date d1(2025, 3, 1);
Date d2(2025, 3, 2);
cout << Less(d1, d2) << endl;
return 0;
}
结合之前Date类实现,如上一段程序中,可以对日期类进行正确比较。但是也有一些情景是需要特殊处理的。
在上面这段程序当中,明显d3>d4的,运行结果为1才对。但运行几次后发现有两个结果,为什么会出现0这个现象呢?
此时我们进行调试观察d3和d4。
可以发现d4的内存地址是大于d3的内存地址,这也解决了我们的疑惑:这里的比较是指针地址的比较,因此需要引入特化或提供函数支持Date比较 。
函数模版特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对<>,<>中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。
使用函数模版特化解决Date*按指针比较的方式。
代码语言:javascript代码运行次数:0运行复制//函数模版特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
普通函数同样可以解决问题,且这种方式简单明了,难道不是更香吗?
代码语言:javascript代码运行次数:0运行复制bool Less(Date* left, Date* right)
{
return *left < *right;
}
我们再来看看传右值的情况(右值指的是匿名对象、常量等)
代码语言:javascript代码运行次数:0运行复制template<class T>
bool Less(const T& left, const T& right) //传右值-->左值引用右值要加const
{
return left < right;
}
//特化
template<>
bool Less<Date*>(Date* const & left, Date* const & right) //const修饰本身
{
return *left < *right;
}
int main()
{
cout << Less(new Date(2025, 3, 1), new Date(2025, 2, 28)) << endl;
return 0;
}
再针对右值的情况下,左值引用要引用右值就必须+const修饰,那在特化的时候,也需要进行对应修改。可以看到,模版函数特化在上面这段程序中显得特别坨,再来看看普通函数的实现。
代码语言:javascript代码运行次数:0运行复制bool Less(Date* const & left, Date* const & right) //const修饰本身
{
return *left < *right;
}
因此,为了提高代码的可读性,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。这样看特化好像无用武之地,别着急我们继续往下看。
类模版特化
全特化
全特化即是将模板参数列表中所有的参数都确定化。
代码语言:javascript代码运行次数:0运行复制template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
};
template<>
class Date<int*, int*>
{
public:
Date()
{
cout << "Date<int*,int*>" << endl;
}
};
template<>
class Date<int, int>
{
public:
Date()
{
cout << "Date<int, int>" << endl;
}
};
偏特化
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
部分模版参数特化
代码语言:javascript代码运行次数:0运行复制template<class T>
class Date<T, char>
{
public:
Date()
{
cout << "Date<T,char>" << endl;
}
};
参数更进一步的限制
代码语言:javascript代码运行次数:0运行复制template<class T1,class T2>
class Date<T1*, T2*>
{
public:
Date()
{
cout << "Date<T1*,T2*>" << endl;
}
};
template<class T1, class T2>
class Date<T1&, T2*>
{
public:
Date()
{
cout << "Date<T1&,T2*>" << endl;
}
};
类模版特化的场景
针对日期进行排序
代码语言:javascript代码运行次数:0运行复制// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
模版分离编译
为什么在 模版【上】章节说到不建议模版声明与定义分离呢?这里做下解释。
分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程。
我们知道C/C++程序运行一般要经历以下步骤:
- 预处理 --> 展开头文件、宏替换、条件编译、去掉注释等;
- 编译 --> 检查语法、生成汇编代码;
- 汇编 --> 将汇编代码转成二进制机器码;
- 链接 --> 合成可执行程序,链接函数地址等;
运行时链接出现了报错,我们来具体分析下原因在哪。
解决方案
- 显示实例化(但是不建议如果有多个类型)
func2(1);
func2(200.1);
- 不做声明和定义分离(避开找地址,在声明处直接有地址了)
总结
优点远大于缺点
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-04-06,如有侵权请联系 cloudcommunity@tencent 删除c++date编译函数效率【优点】 1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生 2. 增强了代码的灵活性 【缺陷】 1. 模板会导致代码膨胀问题,也会导致编译时间变长 2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
发布评论