关于C++模板和重载的小问题
来源:互联网 发布:怪物猎人p3数据库安卓 编辑:程序博客网 时间:2024/04/29 18:57
关于C++模板和重载的小问题
前几天和一位朋友讨论了有关C++模板和重载的一个小问题。我记得最初发现问题的代码是这样的:
#include#include using namespace std;class Node{public:int m;Node(int value) : m(value) {}friend ostream& operator<<(ostream& o, const Node*& p);};ostream& operator<<(ostream& o, const Node*& p){o << p->m << endl;return o;}int main(){Node* p = new Node(10);cout << p << endl;list
l;l.push_back(p);copy(l.begin(), l.end(), ostream_iterator (cout, "/n"));}
上面的代码先用“cout << p”这样的方式显示Node内容,因为前面已经重载了“<<”运算符,显示结果为10,正确。但接下来用copy函数直接把list中的元素“复制”到cout中,以显示Node内容时,程序只显示出了p中存储的内存地址,而非p所指向的Node的内容,这与预期的结果不符合。
我以前没用过ostream_iterator加copy这种直接复制到输出流中的语法(这可以省去自己写循环的麻烦),不是很熟悉其中的机制。一开始,我认为这是因为STL中的copy函数仅是简单地做赋值操作,没有调用重载的operator<<函数,所以没有显示出Node的内容。但很快,那位朋友就指出,虽然copy函数中做的是赋值操作,但ostream_iterator类重载了赋值运算符:
ostream_iterator<_Ty, _Elem, _Traits>& operator=(const _Ty& _Val){// insert value into output stream, followed by delimiter*_Myostr << _Val;if (_Mydelim != 0)*_Myostr << _Mydelim;return (*this);}
这段重载过的代码会调用“<<”运算符函数。也就是说,copy函数没有得到预期结果,这里面一定还有其他我们没有注意到的问题。我仔细想了想,觉得这个问题可以用C++关于模板、重载以及类型隐式转换顺序的语法解释清楚。
首先,对程序的跟踪表明,ostream_iterator的operator=在执行“<<”运算符时,调用的是basic_ostream类中重载的“<<”运算符函数:
_Myt& operator<<(const void *_Val);
这说明,C++编译器在选择参数类型以确定调用哪个重载函数时,选择了const void*,而非我们自己定义的const Node*&。这时我注意到,定义我们自己的operator<<函数时,参数p既然已经是const Node*了,就没必要再加&修饰符了,最简洁的定义方式应该是:
friend ostream& operator<<(ostream& o, const Node* p);
果然,把前面的代码改成纯指针的定义后,copy函数也正确地显示了数字10,这是我们期望的结果,说明copy函数这回正确调用了我们重载的“<<”运算符。可为什么简单的增加一个“&”会让C++编译器调用另一个重载函数呢?
我做了个简单的实验:
void foo(const int*& p){cout << "A" << endl;}void foo(const int* p){cout << "B" << endl;}int main(){int i = 10;int* p = &i;foo(p);}
这段代码的运行结果是A,这说明,当实参类型是指针时,C++编译器会优先匹配那个带&修饰符(即参数为引用类型)的重载函数。这不是和上面的情况正好相反吗?传给copy函数的list里存储的是Node*,basic_ostream类中重载的“<<”参数类型为const void*,而我们原先重载的参数类型为const Node*&,为什么这一回编译器就不调用我们重载的函数呢?这是不是说明Node*经过copy函数内的几次转换后,类型已经不是Node*了呢?
果然,跟踪一下程序的运行过程就会发现,当copy调用ostream_iterator重载的operator=函数时,实参类型是Node*,而operator=函数的形式参数类型是const _Ty&:
ostream_iterator<_Ty, _Elem, _Traits>& operator=(const _Ty& _Val)
这里,_Ty是模板参数,实际就是我们在copy函数里注明的Node*,而组合到const _Ty&中,参数的类型就变成了:
Node* const &
上面这个变化可以从VC.NET的调试器中看到。这说明ostream_iterator重载的operator=函数已经把实参的类型改成了另一种样子(已经不是单纯的指针了),接下来调用“<<”时,编译器就会选择const void*这样的匹配,而非const Node*&。
是不是越说越乱了。还是从头把思路整理一遍吧:
第一、对于下面这样的模板函数:
templatevoid foo(const T val);
当T表示的类型是指针如int*时,const和T的结合体是int* const,而非字面上看到的const int*,这可以用下面的代码来证明:
templatevoid foo(const T val) {}int main(){int i;const int* p = &i; foo (p);// 编译出错int* const q = &i;foo (q);// 可以正确编译运行}
在C++中,int* const和const int*是完全不同的两种类型,前者const是修饰指针,后者const是修饰指针所指向的值。
第二、对于这样的一组重载函数:
void foo(const int* p);void foo(int* const p);
当我们用int* const型的指针作为实参调用时,编译器会选择第2个函数,因为第2个函数的参数类型和实参类型完全相同。但对于这样一组重载函数:
void foo(const int* p);void foo(const int*& p);
当我们同样用int* const型的指针作为实参调用时,编译器会选择第1个函数,因为两个函数参数类型和实参类型都不同,编译器会调用最接近的那个类型(参数的隐式转换匹配顺序,可以参考C++标准中的相关说明)。
这实际就是我们上面遇到的那个问题的答案。basic_ostream类中重载的“<<”参数类型为const void*,我们原先重载的参数类型为const Node*&,而ostream_iterator重载的operator=函数在调用“<<”运算符时,实参类型已经被改成了Node* const &,因此,编译器调用的是ostream_iterator重载的函数,而非我们重载的函数。
所以,当我们把最上面那段程序的“<<”修改为
friend ostream& operator<<(ostream& o, const Node* p);
时,程序可以给出正确的结果。但根据上面的讨论,如果我们把该函数的定义改成:
friend ostream& operator<<(ostream& o, Node* const & p);
程序也可以给出正确的结果。
这只是个小问题,而且是那位朋友编程时偶然遇到的,不过这个问题说明,在C++语言里,参数的定义、隐式转换以及匹配顺序相当重要也相当复杂(C++标准文档里关于这个东西的说明有好长一段),我就很容易在这些地方犯糊涂。
另外,上面的实验都是在VS.NET的C++编译器中做的。那位朋友也在VC6下做了相同的实验,但结果却完全不同。例如,最上面那段程序在VC6下,无论参数类型是指针还是引用,都无法得到正确的结果;奇怪的是,在VC6下,当参数类型是指针时,如果把Node类和“<<”函数的的定义都统统放进std namespace中,居然就可以得到正确结果了。这似乎说明,VC6中的C++编译器在参数匹配顺序方面,并不完全符合C++ 1998标准的定义(也许VC6会优先匹配同一个namespace中的重载函数)。
- 关于C++模板和重载的小问题
- 关于C++模板和重载的小问题
- 转贴:关于C++模板和重载的小问题
- 关于C++模板和重载的小问题
- 关于C++模板和重载的小问题
- 关于C++模板和重载的小问题
- 关于泛型和重载的小问题
- 关于模板函数重载中对象实例化的问题
- 模板类中操作符重载问题("<<"和">>"重载)[c++]
- 关于重载的问题
- 模板重载or编译器的小技巧
- 重载和模板的知识点
- 重载和模板的知识点
- 【足迹C++primer】60、重载和模板
- 模板的小问题
- C关于字符串的一个小问题
- 关于C语言的一个小问题
- 关于函数模板重载的调用顺序
- YC++中如何使用COM接口
- yc++最新版出来了
- 学习xerces-c使用中
- 中国人自己的c/c++编译器、网页浏览器内核
- 网络寒冬下的团队管理
- 关于C++模板和重载的小问题
- C/C++移位运算符出界后的结果是不可预期的
- C++编译器检索VTABLE的具体方法不同
- 在Win2K/XP/2K3中 模拟实现VISITA效果对话框
- C程序员面试
- 右键文件关联
- 在console中 使用C Runtime 和 STL 显示 Unicode中文
- [MSSQL]将用户表 存储过程 变成系统的
- [C++] 获取字体点阵