Effective STL 条款6 : 当心C++另人迷惑的解析

来源:互联网 发布:尚书软件 编辑:程序博客网 时间:2024/05/21 17:12
条款6: 当心C++另人迷惑的解析
 
假如你有一个整数构成的文件,需要把它复制到一个list.以下代码看起来是一个很有意义的方法:
 
ifstream dataFile("ints.dat");
list data(istream_iterator(dataFile), // warning! this doesn't do
               istream_iterator());        // what you think it does
 
 
这里的想法是传递一对istream_iteratorlist的区间构造函数(Item5),来将文件中的整数
拷贝到列表中.
 
这段代码能通过编译,但在运行期,它不做任何事情.它不会从文件中读取任何数据.它也不创建列表.
因为第二条语句并没有声明一个list,也没有调用构造函数.它做了什么呢?..它做的事情是如此古怪.
以至我不敢直接告诉你,你不会相信。我只能一点点的解释。你坐好了吗?如果没有你应该找一把椅子。
 
我们从最基础的开始。这一行代码声明了一个函数f,它具有一个double类型的参数,返回一个int类型的值。
 
Int f(double d);
 
下面一行起到相同的作用。参数d两边的括号是多余的,将被忽略。
 
Int f(double (d));      //same as above; parens around d are ignored
 
底下这行代码声明同一个函数。它没有漏掉了参数名。
 
Int f(double);        //same as above; parameter name is omitted.
 
你也许对以上三个声明形式很熟悉。尽管将参数名放在括号内的能力很新鲜。(很久以前它对我来说也行新鲜)
再多看三个函数声明。第一个声明了一个具有函数指针参数的函数。这个函数指针指向一个无参并返回double类型的函数。
 
Int g(double (*pf)());   //g takes a pointer to a function as a parameter
 
这有另一个方法。唯一不同之处是pf使用了非指针语法(在CC++中都有效)
 
int g(double pf());    //same as above; pf is implicitly a pointer;
 
与平时一样,参数名可以省略。因些这是第三种声明g的方法,消去名称pf
 
Int g(double ());     //same as above, parameter name is omitted
 
注意括号在参数名称上(比如f的第二个声明中的d)和只有括号(这个例子中)的情况。在参数名上的括号会被忽略,但只有括号表示存在一个参数列表:它们表示出现在这里的参数是一个指向函数的指针。
fg的声明热身后,可以来看看开头的代码了。再次列在这里:
 
list data(istream_iterator(dataFile), istream_iterator());
 
打起精神来,这行代码声明了一个函数,名为data,其返回类型是list。函数data有两个参数:
第一个参数名为dataFile。它的类型是istream_iterator。在dataFile两侧的括号被忽略。
第二个参数没有名字。它的类型是指向函数的指针,指向的函数类型为无参,返回值类型是istream_iterator
 
吃惊吗?但是它说明了一个C++的通用规则,将尽可能多的东西可以被解析为函数声明。如果你使用过一段时间的C++,你肯定碰到过这条规则的其它宣告形式。你多少次发现过这种错误呢?
class Widget {...}; // assume Widget has a default constructor
Widget w();      //'uh oh...
这并未声明一个名为wWidget,它声明了一个名为w无参函数,其返回值为一个Widget。学习并认识这一点就像通过了真正C++程序员的仪式。
这些都很有趣(在它曲折的形式上),但是它不能帮助我们声明我们希望得到的东西:定义一个list对象由文件的内容初始化。现在我们知道如何防止它被解析,这很容易表示。C++不许将形参声明放在括号之内,但函数调用的参数却可以带上括号。因些加上一对括号可以强制编译器用我们的方式读代码。
list data((istream_iterator(dataFile)),  // note new parens
istream_iterator0);                      // around first argument
// to list's constructor
这种形式声明data,使用istream_iterator工具定义一组区间,使用区间构造函数。(再一次,参见条款5),它很值得这样做。
不幸,并不是所有编译器知道这一点。我测试过一些编译器,有一半拒绝data的声明(正确形式),除非使用没有附加括号的不正确形式。
(原文在此:Of the several I tested, almost half refused to accept data's declaration unless it was incorrectly declared without the additional parentheses!
为了安抚这些编译器,你只有闭上眼睛,使用我痛苦的说明为错误的声明方式。但是这是不可移植的,同时也是短视的。最后,现在解析错误的编译器未来总会更正,对吗?(当然)
最好的解决方案是在data声明中不使用流行的匿名istream_iterator对象,简单地给出这些迭代器的名字。以下代码在任何地方都可以工作:
ifstream dataFile(" ints.dat"};
istream_iterator dataBegin(dataFile);
istream_iterator dataEnd;
list data(dataBegin. dataEnd);
这段代码与通用的STL风格不同,它使用了命名的迭代器对象,但是你需要决定使用对于编译器和程序员看来都有岐意的代码是否值得。
原创粉丝点击