zoukankan      html  css  js  c++  java
  • Effective STL 笔记: Item 6--Be alert for C++'s most vexing parse

    假设有个文件里面记录的一系列的 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 above
    6: 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);
    

    这种写法应该通用。

  • 相关阅读:
    【2021-08-25】连岳摘抄
    【2021-08-24】对意义的过度扭曲
    【2021-08-23】枕边语
    【2021-08-22】连岳摘抄
    【2021-08-21】旅历尚浅
    【2021-08-20】做事情,等对应好角色去思考
    索引缓冲对象(EBO或IBO )的理解
    vao, vbo的一点拙见
    兔队线段树
    「具体数学」二:和式
  • 原文地址:https://www.cnblogs.com/yangyingchao/p/3394146.html
Copyright © 2011-2022 走看看