非类型模板参数如何使用&非类型模板参数使用时的注意事项&如何控制模板的实例化以节省内存空间
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了非类型模板参数如何使用&非类型模板参数使用时的注意事项&如何控制模板的实例化以节省内存空间,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含12659字,纯文字阅读大概需要19分钟。
内容图文
非类型模板参数
含有非类型模板参数的函数在重载时的注意事项
形式一:
#include?<iostream>??
using?namespace?std;??
#include?<vector>??
#include?<algorithm>??
??
template?<typename?T,?int?val>??
T?AddValue(T?const&?obj)??
{??
????return?obj?+?val;??
}??
??
int?main()??
{??
????vector<int>?Vector_Obj1{?1,2,3,4,5,6?},?Vector_Obj2;??
????Vector_Obj2.resize(Vector_Obj1.size());??
????transform(Vector_Obj1.begin(),?Vector_Obj1.end(),?Vector_Obj2.begin(),?(int(*)(int?const&))AddValue<int,?5>);??
????for_each(Vector_Obj2.begin(),?Vector_Obj2.end(),?[](const?int&?obj)?{cout?<<?obj?<<?"?";?});??
}?
形式二:
#include?<iostream>??
using?namespace?std;??
#include?<vector>??
#include?<algorithm>??
??
template?<typename?T,?int?val>??
T?AddValue(T?const&?obj)??
{??
????return?obj?+?val;??
}??
??
int?main()??
{??
????vector<int>?Vector_Obj1{?1,2,3,4,5,6?},?Vector_Obj2;??
????Vector_Obj2.resize(Vector_Obj1.size());??
????transform(Vector_Obj1.begin(),?Vector_Obj1.end(),?Vector_Obj2.begin(),?AddValue<int,?5>);??
????for_each(Vector_Obj2.begin(),?Vector_Obj2.end(),?[](const?int&?obj)?{cout?<<?obj?<<?"?";?});??
}??
我们看到上述两种形式,最大的区别就在于:
①transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (int(*)(int const&))AddValue<int, 5>);
② transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), AddValue<int, 5>);
我们有时就会产生疑问:这两种代码输出结果相同,但为什么形式一中要进行函数指针的强制类型转换呢?
在《C++ templates》中有这样一段话,引人深思:
这段话的意思是说:
AddValue<int,5>相当于一个函数指针,对于C++重载出的很多同名但是参数不同的函数来说,单纯的看AddValue这个独立的函数指针不看参数类型,我根本不知道AddValue这个函数指针指向函数的那个重载版本。虽然编译器可以在Transform中通过观察判断传递给AddValue函数指针的参数可以判断出是调用了那个函数重载版本,但是我们不清楚呀!因此我们要进行强制类型转换,虽然强转与否没啥卵用,但是至少可以使我们思路清晰,直到这个函数的到底调用了那个函数重载版本去操作的这一堆数据。
举例说明:
#include?<iostream>??
using?namespace?std;??
#include?<vector>??
#include?<algorithm>??
??
template?<typename?T,?int?val>??
T?AddValue(T?const&?obj)??
{??
????return?obj?+?val;??
}??
template?<typename?T,?int?val>??
double?AddValue(double?const&?obj)??
{??
????return?(obj?+?(double)val)?/?2;??
}??
??
int?main()??
{??
????vector<int>?Vector_Obj1{?1,2,3,4,5,6?};??
????vector<double>?Vector_Obj2;??
????Vector_Obj2.resize(Vector_Obj1.size());??
????transform(Vector_Obj1.begin(),?Vector_Obj1.end(),?Vector_Obj2.begin(),?(double(*)(double?const&))AddValue<int,?5>);??
????for_each(Vector_Obj2.begin(),?Vector_Obj2.end(),?[](const?double&?obj)?{cout?<<?obj?<<?"?";?});??
}??
这个例子中,强制类型转换就很有必要了,没有了强制类型转换,编译器根本仅仅根据一个光溜溜的函数指针根本识别不出到底要执行那个函数重载版本,当编译器一出现徘徊不定的现象时,错误警告就接踵而至:
非类型模板的参数限制
① 不可以是浮点型类型的量(变量/常量):
匹配模板特化时,编译器会匹配模板参数,包括非类型参数。
就其本质而言,浮点值并不精确,并且C ++标准未指定它们的实现。因此,很难确定两个浮点非类型参数何时真正匹配:
template <float f> void foo () ;void bar () {
??? foo< (1.0/3.0) > ();
foo< (7.0/21.0) > ();
}
这些表达式不一定产生相同的“位模式”,因此不可能保证它们使用相同的特化 - 没有特殊的措辞来涵盖这一点。
② 非类型模板参数不能使用内部链接对象(例如string)作为实参
当我们在一个项目的多个源文件中使用相同的string字符串实例化非类型参数模板时,按理说相同的string字符串实例化出来的非类型参数模板应该是相同的,但是别忘了,我们实例化非类型参数模板是在不同的源文件中,不同源文件中的string字符串常量被存储在不同区域中,不同区域的数据实例化出来的非类型参数模板对于编译器来说是不相同的,这就有些可怕了,在我们看来相同的string字符串应该实例化出相同的模板才对,但是由于相同的string字符串定义文件不同,导致最终实例化出的模板不相同,针对于这种情况,编译器才给出了“非类型模板参数的形参不可以使用内部链接对象”。
如何使用string类型作为非类型模板参数呢?(改变链接属性)
C++规定,有const修饰的变量,不但不可修改,还都将具有内部链接属性,也就是只在本文件可见。(这是原来C语言的static修饰字的功能,现在const也有这个功能了。)又补充规定,extern const联合修饰时,extern将压制const这个内部链接属性。于是,extern const string将仍然有外部链接属性,但是还是不可修改的。
showing.hpp
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
template?<const?string&?obj>??
void?ShowInf()??
{??
????cout?<<?obj?<<?endl;??
}??
main.cpp
#include?<iostream>??
#include?"ShowInf.hpp"??
using?namespace?std;??
??
extern?const?string?str?=?"hello?world!";??
??
int?main()??
{??
????ShowInf<str>();??
}
???????
输出结果:
注意:extern只能存在于main.cpp中函数体的外部,以下情形是不对的:
#include?<iostream>??
#include?"ShowInf.hpp"??
using?namespace?std;??
????
int?main()??
{??
????extern?const?string?str?=?"hello?world!";??????
????ShowInf<str>();??
}??
Const char*变量为什么不可以呢?
你得用下述的方式传递const char数组:
Main.cpp
#include?<iostream>??
#include?<string>??
#include?"Person.hpp"??
#include?"ShowInf.hpp"??
using?namespace?std;??
??
const?char?s[]?=?"hello";??// 加不加extern都OK
??
int?main()??
{??
????ShowInf<s>();??
}??
ShowInf.hpp
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
template?<const?char*?obj>??
void?ShowInf()??
{??
????cout?<<?obj?<<?endl;??
}??
注意:变量s一定要声明为:const char s[] = "hello",不要声明为const char* s = “hello”!
那用上述方式可不可以使用类类型作为非类型模板的实参呢?
我做了如下尝试,得出的答案是:不可以。
因为类类型是不可以作为编译期常量的。其实,你可以这样的巧记:类类型是多个基本数据类型的有机结合,float,double浮点型况且都不可以作为非类型模板的参数,类类型怎么又可以担当这个大任呢!
Person.hpp
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
class?Person??
{??
private:??
????int?age;??
????string?name;??
public:??
????Person(int?age,?string?name)??
????{??
????????this->age?=?age;??
????????this->name?=?name;??
????}??
????void?Define(int?age,?string?name)??
????{??
????????this->age?=?age;??
????????this->name?=?name;??
????}??
????friend?ostream&?operator?<<?(ostream&?ostr,?Person&?obj);??
};??
??
ostream&?operator?<<?(ostream&?ostr,?Person&?obj)??
{??
????ostr?<<?obj.name?<<?"的姓名为"?<<?obj.age;??
????return?ostr;??
}??
Showing.hpp
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
template?<const?Person&?obj>??
void?ShowInf()??
{??
????cout?<<?obj?<<?endl;??
}??
Main.cpp
#include?<iostream>??
#include?<string>??
#include?"Person.hpp"??
#include?"ShowInf.hpp"??
using?namespace?std;??
??
extern??Person&?obj;??
??
int?main()??
{??
????obj.Define(19,?"张三");??
????ShowInf<obj>();??
}
???????
输出错误
什么是内部链接和外部链接?
内部链接就是该符号只在编译单元内有效,其他编译单元看不到。所以 多个编译单元中可有相同符号。const变量可以出现在多个.cpp文件中 而不会冲突就是因为是内部链接。
外部链接就是其他编译单元能看到当前编译单元的符号。如果有相同的外部链接符号,就会在链接时报重定义符号的错误。
对于参数为类型的模板,相同类型实例化出的模板会由于所属文件不同而被存储在不同存储区域,这对于我们宝贵的内存来说是一种非常嚣张的浪费,我们应该如何避免这种铺张浪费的情况呢?
模板实例化:
当模板被使用时才会实例化,这一特性意味着,相同的实例可能出现在多个对象文件中。举个例子就是说,当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中都会有该模板适用该参数的一个实例。
上述的问题在小程序里不算什么,但是在一个大的程序中,在多个文件中实例化相同模板的额外开销会非常严重。在新标准中,我们可以通过控制显式实例化来避免这种开销。
控制实例化
与多文件中声明一个变量相同,可以使用关键字extern来承诺在程序其他位置会有一个该实例化的非extern声明,只需要在链接成可执行程序时将含有实例化的.o文件链接上就可以了。因此使用关键字extern进行一个模板的声明时不会在本文件中生成实例化代码。
如下演示一下具体如何操作:
第一个文件主要用来定义模板(当然这里的第一个文件代指了我们在项目中定义的许多模板文件):
// Stack.hpp
#include?<iostream>??
using?namespace?std;??
#include?<vector>??
??
//?模板的缺省值??
template?<class?T,?class?CONT?=?vector<T>?>??
class?Stack??
{??
private:??
????CONT?element;??
????int?Size;??
public:??
????Stack();??
????Stack(T?obj);??
????Stack(vector<T>?obj);??
??
????void?Push(T?obj);??
????void?Pop();??
????T&?Top();??
????bool?Empty();??
????int?PrintSize();??
????T&?At(int?order);??
};??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
T&?Stack<T,?CONT>::At(int?order)??
{??
????if?(order?>=?this->Size)??
????{??
????????throw?out_of_range("over?array?range!");??
????}??
????return?this->element.at(order);??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
int?Stack<T,?CONT>::PrintSize()??
{??
????return?this->Size;??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
bool?Stack<T,?CONT>::Empty()??
{??
????return?this->element.empty();??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
T&?Stack<T,?CONT>::Top()??
{??
????if?(this->Size?==?0)??
????{??
????????throw?out_of_range("Stack?is?empty!");??
????}??
????return?*(this->element.end());??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
void?Stack<T,?CONT>::Pop()??
{??
????if?(this->Size?==?0)??
????{??
????????throw?out_of_range("Stack?is?empty!");??
????}??
????this->element.pop_back();??
????this->Size--;??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
void?Stack<T,?CONT>::Push(T?obj)??
{??
????this->element.push_back(obj);??
????this->Size++;??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
Stack<T,?CONT>::Stack(vector<T>?obj)??
{??
????this->Size?=?0;??
????this->element.clear();??
??
????this->element?=?obj;??
????this->Size?=?obj.size();??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
Stack<T,?CONT>::Stack(T?obj)??
{??
????this->Size?=?0;??
????this->element.clear();??
??
????this->element.push_back(obj);??
????this->Size++;??
}??
??
template?<class?T,?class?CONT?/*=?vector<T>?*/>??
Stack<T,?CONT>::Stack()??
{??
????this->Size?=?0;?//?赋值前一定要初始化??
????this->element.clear();??
}??
在第二个文件中主要定义我们要在项目中使用到的所有实例化模板:
// TemplateBuild.cpp
#include?"Stack.hpp"??
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
template?Stack<string>;?//?加不加template关键字都OK??
template?Stack<int>;??
第三个文件中通过外部链接我们可以使用之前已经定义好的实例化模板:
// TemplateApplication.cpp
#include?"Stack.hpp"??
#include?<iostream>??
#include?<string>??
using?namespace?std;??
??
extern?template?Stack<string>;??
??
int?main()??
{??
????Stack<string>?Stack_Obj1("张三");??
????cout?<<?Stack_Obj1.At(0)?<<?endl;??
}
???????
在编译时,一个项目中的每个.cpp文件(TemplateApplication.cpp & TemplateBuild.cpp)都将会编译成相应的.o文件,编译器编译完后的下一步工作就是根据“外部链接关键字extern”将项目中得到的每个.o文件(TemplateApplication.o & TemplateBuild.o)链接到一起,生成可执行文件。
注意:由于我们使用外部链接,链接外部的已经实例化的模板并且使用,因此编译器需要实例化模板的所有成员,这与我们普通的模板实例化不同(不同模板实例化中,用到啥成员就实例化啥成员),这种实例化模板的方式会实例化模板的所有成员。
非类型参数模板的形参要求
非类型模板参数是有限制的,通常是常整数(包括枚举值)或者指向外部链接对象的指针/引用。
模板参数:常整数(int,short,long……可在编译器确定的整数数据类型)
#include?<iostream>??
using?namespace?std;??
??
template?<unsigned?int?N>??
void?ShowInf()??
{??
????cout?<<?N?<<?endl;??
}??
??
int?main()??
{??
????const?int?a?=?10;??
????ShowInf<a>();??
}??
模板参数:指向外部链接对象的指针/引用
#include?<iostream>??
using?namespace?std;??
??
template?<typename?T,?T(*Func)(const?T&?val1,const?T&?val2)?>??
T?Operator(T?para1,?T?para2)??
{??
????return?Func(para1,?para2);??
}??
??
int?Add(const?int&?para1,?const?int&?para2)??
{??
????return?para1?+?para2;??
}??
??
int?main()??
{??
????int?para1?=?9,?para2?=?10;??
????cout?<<?Operator<int,?Add>(para1,?para2)?<<?endl;??
}?
???????
上述代码展示了,模板参数为函数指针时的代码书写格式。
模板参数:模板
#include?<iostream>??
using?namespace?std;??
??
template<typename?T>??
class?Print??
{??
public:??
????void?operator()(const?T&?val)??
????{??
????????cout?<<?val?<<?"?";??
????}??
};??
??
template<typename?T>??
class?Inc??
{??
public:??
????void?operator()(T&?val)??
????{??
????????val++;??
????}??
};??
??
template<typename?T>??
class?Dec??
{??
public:??
????void?operator()(T&?val)??
????{??
????????val--;??
????}??
};??
??
template<?typename?T,?template<typename?T>?class?Func>??
void?Foreach(T?Array[],?unsigned?size)??
{??
????Func<T>?FuncA;??
????for?(int?i?=?0;?i?<?size;?i++)??
????{??
????????FuncA(Array[i]);?//?[]为数组索引符号??
????}??
????cout?<<?endl;??
}??
??
int?main()??
{??
????int?Array[]?=?{?1,2,3,4,5,6,7,8,9,10?};??
????Foreach<int,?Inc>(Array,?10);??
????Foreach<int,?Print>(Array,?10);??
????Foreach<int,?Dec>(Array,?10);??
????Foreach<int,?Print>(Array,?10);??
}??
运行结果:
内容总结
以上是互联网集市为您收集整理的非类型模板参数如何使用&非类型模板参数使用时的注意事项&如何控制模板的实例化以节省内存空间全部内容,希望文章能够帮你解决非类型模板参数如何使用&非类型模板参数使用时的注意事项&如何控制模板的实例化以节省内存空间所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。