Loading... # > 成员函数有自己的模板参数 类模板中成员函数和普通类中成员函数创建时机是有区别的: * 普通类中的成员函数一开始就可以创建 * **类模板中的成员函数在调用时才创建** # 一、基本概念、构造函数模板 1. 类模板中的成员函数,只有源程序代码中出现调用这些成员函数的代码时,这些成员函数才会出现在一个实例化了的类模板中。 ```cpp #include <iostream> using namespace std; template <typename T1> class A { public: void myftmp(int tmpt) //普通成员函数 { cout << "myftmp" << endl; } template <typename T2> A(T2 v1, T2 v2); //构造函数模板,引入了自己的模板参数T2,与类A的模板参数T1没有关系 template <typename T3> void myft(T3 tmpt) //普通成员函数模板 用T3 { cout << tmpt << endl; } T1 m_ic; static constexpr int m_stcvalue = 200; public: //增加两个构造函数 不是构造函数模板 A(double v1, double v2) { cout << "A::A(double,double)执行了!" << endl; } A(T1 v1, T1 v2) { cout << "A::A(T1,T1)执行了!" << endl; } }; //在类外实现类模板的构造函数模板 T1 和 T2的顺序不能反! 顺序一旦反了的话就会编译报错 template <typename T1>//T1是类模板需要的 template <typename T2>//T2是构造函数需要的 A<T1>::A(T2 v1, T2 v2) { cout << "A::A(T2,T2)执行了!" << endl; } int main() { A<float> a(1, 2); //实例化了A<float>这样一个类型,并用int类型来实例化构造函数 A::A(T2,T2)执行了! a.myft(3); //3 a.myft<int>(3);//通过3可以自动推导出为int类型 可以不用手动加<int> T3可以推断为int A<float> a2(1.1, 2.2); //A<float>类型已经被上面的代码实例化过了,并用doule类型来实例化构造函数,因为1.1和2.2都是double类型 A::A(double,double)执行了! A<float> a3(11.1f, 12.2f);//用float类型来实例化构造函数,因为11.1f和12.2f都是float类型 A::A(T1,T1)执行了! a.myftmp(4); return 0; } ``` 1. **类模板中的成员函数模板,只有源程序代码中出现调用这些成员函数模板的代码时,这些成员函数模板的具体实例才会出现在一个实例化了的类模板中**。 `只有调用了a.myft(3); 才会有这个函数的实例化`。 `即便是普通成员函数a.myftmp(4); 也是调用了之后才会实例化`。 1. 目前编译器并不支持虚成员函数模板, 因为虚函数表vtbl的大小是固定的。但是成员函数模板只有在被调用的时候才能被实例化出来,不然编译器也不知道要用什么模板来实例化,虚函数可能不被实例化出来,那么vtbl的大小就变为不固定的 ```cpp /*template <typename T4> virtual void myvirfunc() {}*/ ``` 3.1 C++之父的说法:如果允许虚函数模板,则每次有人用新的参数类型调用该虚函数模板时,就必须给对应的虚函数表再增加一项,这意味着只有链接程序才能去构造虚函数表并在表中设置有关函数,因此,成员函数模板绝不能是虚的。 3.2. **类模板中可以有普通的虚成员函数(虚函数)**,这并没有什么问题。大家都知道,普通成员函数如果不被调用的情况下不会被实例化出来。 3.3 但是,对于虚函数,不管是否调用,编译器都会把他实例化出来,因为编译器要创建虚函数表vtbl,该表中的每个具体表项都对应一个虚函数地址,所以编译器必然得把所有虚函数都实例化出来。 # 二、拷贝构造函数模板与拷贝赋值运算符模板 > 请大家注意区分:拷贝构造函数模板不是拷贝构造函数,拷贝赋值运算符模板不是拷贝赋值运算符(构造函数模板也不是构造函数);{一个是A<T>,一个是A<U>} 因为拷贝构造函数或者拷贝赋值运算符要求拷贝的对象类型完全相同,而拷贝构造函数模板和拷贝赋值运算符模板就没有这种要求。 ```cpp #include <iostream> using namespace std; template <typename T1> class A { public: void myftmp(int tmpt) //普通成员函数模板 用T3 { cout << "myftmp" << endl; } template <typename T2> A(T2 v1, T2 v2); //构造函数模板,引入了自己的模板参数T2,与类A的模板参数T1没有关系 template <typename T3> void myft(T3 tmpt) //普通成员函数模板 用T3 { cout << tmpt << endl; } T1 m_ic; static constexpr int m_stcvalue = 200; public: //增加两个构造函数 不是构造函数模板 A(double v1, double v2) { cout << "A::A(double,double)执行了!" << endl; } A(T1 v1, T1 v2) { cout << "A::A(T1,T1)执行了!" << endl; } public: //拷贝构造函数模板 template <typename U> A(const A<U>& other) //A(A<U>& other) { cout << "A::A(const A<U>& other)拷贝构造函数模板执行了!" << endl; } //拷贝赋值运算符模板 template <typename U> //----------这个U和上面的U没有任何关系 A<T1>& operator=(const A<U>& other) //A<T1>& operator=(A<U>& other) { //..... cout << "operator=(const A<U>& other)拷贝赋值运算符模板执行了!" << endl; return *this; } }; //在类外实现类模板的构造函数模板 T1 和 T2的顺序不能反 顺序一旦反了的话就会编译报错 template <typename T1>//T1是类模板需要的 template <typename T2>//T2是构造函数需要的 A<T1>::A(T2 v1, T2 v2) { cout << "A::A(T2,T2)执行了!" << endl; } int main() { A<float> a(1, 2); //实例化了A<float>这样一个类型,并用int类型来实例化构造函数 A::A(T2,T2)执行了! a.myft(3); //3 a.myft<int>(3);//通过3可以自动推导出为int类型 可以不用手动加<int> a.myftmp(4); A<float> a2(1.1, 2.2); //A<float>类型已经被上面的代码实例化过了,并用doule类型来实例化构造函数,因为1.1和2.2都是double类型 A::A(double,double)执行了! A<float> a3(11.1f, 12.2f);//用float类型来实例化构造函数,因为11.1f和12.2f都是float类型 A::A(T1,T1)执行了! //不会去执行拷贝构造函数中的模板 会去调用拷贝构造函数 A<float> a4(a3); //会执行拷贝构造函数模板中的代码吗(不会) //拷贝构造函数模板什么时候被调用?类型不同(都是用类模板A实例化出来的,比如A<double>与A<float>)的两个对象,用一个拷贝构造另一个时。 A<int> a5(a3); //a3是A<float>类型,a5是A<int>类型,两者类型不同 {两者类型不同就会触发拷贝构造函数的执行} //---------------- ////赋值构造函数模板什么时候被调用?类型不同(都是用类模板A实例化出来的,比如A<double>与A<float>)的两个对象,用一个拷贝赋值另一个时。 a3 = a4;//a3是 a3 = a5; return 0; } ``` 发现两个问题: a)\_nmsp1::A<float> a4(a3);代码行没输出任何结果,这表示拷贝构造函数模板中的代码没有执行。 b)a4.m\_ic值的确变成了16.2了。这说明确实是通过a3拷贝构造生成的a4。 a3,a4类型相同(A<float>),本该执行拷贝构造函数,但是因为类模板A中没有拷贝构造函数,所以编译器内部实际是 执行了按值拷贝的一个动作,使a4.m\_ic值变成了16.2了。(编译器增加) 拷贝构造函数模板永远不可能成为拷贝构造函数。编译器不会用调用拷贝构造函数模板来代替调用拷贝构造函数。{死记} //拷贝构造函数模板什么时候被调用?类型不同(都是用类模板A实例化出来的,比如A<double>与A<float>)的两个对象,用一个拷贝构造另一个时。 //拷贝赋值运算符模板永远不可能成为拷贝赋值运算符(除非A(A<U>& other) 拷贝构造函数不加const类型) # 三、成员函数模板特化 ```cpp #include <iostream> using namespace std; template <typename T1> class A { public: template <typename T2> A(T2 v1, T2 v2); //构造函数模板,引入了自己的模板参数T2,与类A的模板参数T1没有关系 template <typename T3, typename T4> //普通成员函数模板 void myft(T3 tmpt,T4 tmpt2) { cout << "myft()泛化版本" << endl; cout << tmpt << endl; cout << tmpt2 << endl; } template <typename T4> //偏特化 void myft(int tmpt, T4 tmpt2); //{ // cout << "myft(int,T4)偏特化版本" << endl; // cout << tmpt << endl; // cout << tmpt2 << endl; //} template <> //全特化 全特化只能内类实现 无法类外实现 void myft(int tmpt, float tmpt2) { cout << "myft(int,float)全特化版本" << endl; cout << tmpt << endl; cout << tmpt2 << endl; } public: T1 m_ic; static constexpr int m_stcvalue = 200; }; //在类外实现类模板的构造函数模板 template <typename T1> template <typename T2> A<T1>::A(T2 v1, T2 v2) { cout << "A::A(T2,T2)执行了!" << endl; } //在类外实现类模板A的myft成员函数模板的偏特化版本 template <typename T1> template <typename T4> void A<T1>::myft(int tmpt, T4 tmpt2) { cout << "myft(int,T4)偏特化版本" << endl; cout << tmpt << endl; cout << tmpt2 << endl; } //在类外实现类模板A的myft成员函数模板的全特化版本 --> 这种写法编译不过!! //template <typename T1> //template <> //全特化 //void A<T1>::myft(int tmpt, float tmpt2) //{ // cout << "myft(int,float)全特化版本" << endl; // cout << tmpt << endl; // cout << tmpt2 << endl; //} int main() { A<float> a2(1, 2); a2.myft(3.1, 2); a2.myft(3, 2); a2.myft(3, 2.5f); // A<float> a3; // a3.myft(3, 2.5f); // a3.myft(3.1,2); return 0; } ``` **目前的C++标准不允许在类模板之外全特化一个未被特化的类模板(指的是类模板A)的成员函数。** 解决办法:增加一个类A的特化版本后,再增加一个函数模板 ```cpp template <> class A<float> { public: template <typename T3, typename T4> //普通成员函数模板 void myft(T3 tmpt, T4 tmpt2) { cout << "类A特化版本的myft()泛化版本" << endl; cout << tmpt << endl; cout << tmpt2 << endl; } //template <> //全特化 //void myft(int tmpt, float tmpt2); }; //A<float>中有泛化版本的myft,因此不用在A<float>中声明如下的全特化版本。 template <> //全特化 void A<float>::myft(int tmpt, float tmpt2) { cout << "类A特化版本的myft(int,float)全特化版本" << endl; cout << tmpt << endl; cout << tmpt2 << endl; } ``` 整体感觉:类模板中的成员函数全特化可能还不算太完善,写代码时要注意测试。 在实际工作中,建议把这些特化版本写在类模板内部,然后类模板一般也要写在头文件。 最后修改:2025 年 06 月 30 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏