Effective STL Item 6: Be alert for C++’s most vexing parse.

来源:互联网 发布:spu sku 数据库设计 编辑:程序博客网 时间:2024/06/11 20:54

为什么这段 c++ 代码需要加这对括号才正确运行,是编译器问题吗?

正确运行的代码:
std::ifstream fin("test.txt");std::string str((std::istreambuf_iterator<char>(fin)), // 这里多了一对括号         std::istreambuf_iterator<char>());cout << str;
错误代码:
std::ifstream fin("test.txt");std::string str(std::istreambuf_iterator<char>(fin),  // 这里少了括号        std::istreambuf_iterator<char>());cout << str;

第二段代码的错误信息:
1>源.obj : error LNK2019: 无法解析的外部符号 "class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl str(class std::istreambuf_iterator<char,struct std::char_traits<char> >,class std::istreambuf_iterator<char,struct std::char_traits<char> > (__cdecl*)(void))" (?str@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$istreambuf_iterator@DU?$char_traits@D@std@@@2@P6A?AV32@XZ@Z),该符号在函数 _main 中被引用
1>C:\Code\C++\C++ Primer\Test\Debug\Test.exe : fatal error LNK1120: 1 个无法解析的外部命令


前一段代码可以输出文件里的内容,但是后一段代码出错?
两段代码都没语法错误吧,为什么结果不一样?



假设有个文件里面记录的一系列的 int 值,现在我们想把这些数值存到一个 List 里面,结合 Item 5, 我们可能会写出下面的代码:

ifstream dataFile("ints.data");list<int> data(istream_iterator<int>(dataFile), // Start of iterator               istream_iterator());             // End of iterator

这段代码可以编译,但运行时并不工作,它不会去调用 list 的构造函数,从而不会生成我们想要的这个 List。

问题,出在 C++ 对代码的解析上。

假设我们需要声明一个函数,该函数接受 double 类型参数并返回 int 类型,C++ 里面,下面三种方法是等效的:

1: int f(double d);  // Old C style.2: int f(double(d)); // Function style casts.3: int f(double);    // Same as first but skip parameter.

如果我们要声明另外一个函数,该函数同样返回 int,但接受的参数是一个无参数但返回 double 的函数的指针,则下面的声明是等效的:

4: int g(double (*pf)()); // g takes a pinter to a function as paramter.5: int g(double pf());    // same as above6: int g(double ());      // same as above, but parameter (function name) skipped.

观察 1 ~ 6 我们可以看到“ 括号”在不同位置时候的不同作用:

  • 参数 周围的括号可以被忽略
  • 单独 的括号实际上意味着这是一个函数指针的参数列表!

了解了这个区别之后再返回来看最开始的那个声明:

list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());

这个声明定义了一个返回 list<int> 的函数,该函数接受两个参数:

  • 第一个参数名为 dataFile,类型为 istream_iterator<int>,dataFile 两遍的括号可以忽略。
  • 第二个参数是一个函数指针,该函数不接受参数但返回一个 istream_iterator<int>。

这个和我们最开始想象的完全不一样,而产生分歧的原因就在于 C++ 对代码的解析上:

    只要表达式可以被解析成 函数 ,那么该表达式 就会被编译器解析成函数 

想象一下下面这段代码,相信很多人都写出来过,但是它能编译么?

 7: class Widget 8: { 9: public:10:     Widget(){}11:     virtual ~Widget(){}12:     void Show(){}13: };14: 15: Widget w();16: w.Show();

上面片段的第 8 行实际上不是声明了一个 Widget 对象,而是声明了一个用来返回 Widget 对象的函数,第 9 行自然也就出错了。

理解了上面的内容,也就可以想想怎么解决开始时提出的问题了:给形参声明加上括号不合法,但给函数调用的实参加括号是合法的,通过适当的添加括号,问题得以解决:

1: list<int> data((istream_iterator<int>(dataFile)),2:                istream_iterator<int>());

这里第一个参数周围添加了多余的括号,假设编译器仍然认为 data() 是一个函数声明,则第一个形参周围就被添加了括号,这是一个非法的行为,所以编译器会丢掉这种可能,转而匹配下一种可能得匹配,认为该表达式声明了一个 list<data> 的变量,并调用适当的区间函数(Item 5)来进行初始化。

但并非所有的编译器都支持这种匿名对象,如果编译器不支持,我们需要下面的这种显示的写法:

1: ifstream dataFile("ints.dat");2: istream_iterator<int> dataBegin(dataFile);3: istream_iterator<int> dataEnd;4: list<int> data(dataBegin, dataEnd);

这种写法应该通用。

0 0
原创粉丝点击