c++ 写编译器 解析自己的语言,当心C++编译器最烦人的分析机制

假设有一个存储整数的文件, 想把这些整数复制到一个std::list中去, 下面是一个很合理的做法,

std::ifstream_DataFile("/home/Tong_G/Test.txt");

std::list_Data(std::istream_iterator(_DataFile),

std::istream_iterator());

这种做法的思路是, 把一对std::istream_iterator传入std::list的区间构造函数中, 从而把文件的整数复制到std::list中去.

这段代码可以通过编译, 但是运行时它什么也不会做, 它不会从文件中读去任何数据, 不会创建std::list, 这是因为第二条语句并没有声明一个std::list对象, 也没有调用构造函数, 它做了一件很操蛋的事...

但是为什么第二条语句做的事情与我们期望的事情大相径庭呢? 要从C++蛋疼的语法分析机制说起:

下面这行代码声明了一个带double参数并返回int的函数:

int_Func(double_InDouble);

下面这行代码做了同样的事情.参数_InDouble两边的括号是多余的,会被忽略:

int_Func(double(_InDouble) );

下面这行代码声明了同样的函数,只是它省略了参数名称:

int_Func(double);

现在我们再来看三个函数声明,第一个声明了一个函数_Fuck(),他的参数是一个只想不带任何参数并返回一个double类型的函数的指针:

int_Fuck(double(*_ptrFunc)());

有另外一种方式可以表明同样的意思, 唯一的区别是, _ptrFunc用非指针的形式来声明(这种形式在C语言和C++中都有效):

int_Fuck(double_ptrFunc());

跟通常一样, 参数名称可以忽略, 因此下面是_Fuck()的第三种声明, 其中参数名_ptrFunc被省略了:

int_Fuck(double());

请注意围绕参数名的括号(比如_Func()的第二个声明中的_InDouble)与独立的括号的区别, 围绕参数名的括号被忽略, 而独立的括号则表明参数列表的存在: 它们说明存在一个函数指针的参数.

在了解了_Func()和_Fuck()的声明后, 我们开始考虑最开始的那段代码的问题:

std::ifstream_DataFile("/home/Tong_G/Test.txt");

std::list_Data(std::istream_iterator(_DataFile),

std::istream_iterator());

这两行代码究竟做了什么? 再重复一下写这段代码时我们的本义:

创 建一个std::list< int >对象_Data, 构造该对象的方法是: 把一对std::istream_iterator传入std::list< int >的区间构造函数中, 从而把文件中的整数复制到std::list< int >中去.

这是我们的本义, 但是编译器其实是这么理解的:

这里声明了一个函数, _Data(), 其返回值为std::list< int >, 这个_Data()函数有如下两个参数:

第一个参数的名称是_DataFile,它的类型是std::istream_iterator< int >. _DataFile两边的括号是多余的,会被忽略

第二个参数没有名称.它的类型是一个”不带任何参数并返回一个std::istream_iterator< int >的函数”的指针.

蛋碎!!!!!!

但是这个解释却与C++中的一条普通规律相符: 尽可能的解释为函数声明.

如果使用C++编程有一段时间了, 大家应该都遇到过这类错误:

class_Widget{};

intmain(int_Argc,char**_Argv)

{

_Widget_w();

#

return0;

}

好像是我们使用_Widget的默认构造函数构建了一个名为_w的_Widget对象, 但是实际上编译器会把它理解为: 声明了一个名为_w的函数, 该函数不接受任何类型的参数并且返回一个_Widget类型的对象.

这种歧义, 最新版的clang编译器能够察觉, 并抛出警告:

[C++Projects]$clang++-Wall-std=c++11main.cxx

main.cxx:14:15:warning:emptyparenthesesinterpretedasafunction

declaration[-Wvexing-parse]

_Widget_w();

^~

main.cxx:14:15:note:removeparenthesestodeclareavariable

_Widget_w();

^~

1warninggenerated.

[C++Projects]$

这个warning很明确的告诉我们: main.cxx文件的第14行的代码中的空括号被解释为一个函数定义, 但是使用GCC的小伙伴们你们就没有那么幸运了(阴笑).

事实上, 最开始的那段代码在我的机器上使用clang 3.3编译的时候, clang同样会抛出了一个warning:

[C++Projects]$clang++-Wall-std=c++11main.cxx

main.cxx:14:27:warning:parenthesesweredisambiguatedasafunction

declaration[-Wvexing-parse]

std::list_Data(std::istream_iterator(_DataFile),

^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

main.cxx:14:29:note:addapairofparenthesestodeclareavariable

std::list_Data(std::istream_iterator(_DataFile),

^

()

1warninggenerated.

[C++Projects]$

但是在GCC 4.8编译的时候,在打开-Wall开关的情况下也没有收到任何警告, Visual C++没试过...

所有这些都很有意思(通过它自己的歪曲方式),但是这并不能帮助我们做自己想做的事情:我们想用文件的内容初始化std::list< int >对象.

现在我们已经知道必须绕过某一种分析机制,剩下的事情就简单了,把形式参数的声明用括号括起来是非法的,但是给函数参数加上括号却是合法的,所以通过增加一队括号,我们可以强迫编译器按照我们的方式来工作:

std::list_Data((std::istream_iterator(_DataFile)),

std::istream_iterator());

这是声明_Data的正确方式,在使用std::iterator_iterator和区间构造函数时,注意到这一点是有益的.

不幸的是,并不是所有编译器都支持这一点,为了满足所有的编译器,我们可以使用一种更好的方式来绕开这种分析机制:更好的方式是再对_Data的声明中避免 使用匿名的std::istream_iterator对象(尽管匿名对象是一种趋势),而是给这些迭代器一个名称,下面的代码应该是总可以工作的:

std::ifstream_DataFile("/home/Tong_G/Test.txt");

std::istream_iterator_DataBegin(_DataFile);

std::istream_iterator_DataEnd;

std::list_Data(_DataBegin,_DataEnd);

这段代码成功的读取了我的位于/home/Tong_G文件夹中Test.txt文件中的内容, 检测一下:

for(constint&_Elem:_Data)

std::cout<<_elem>

std::cout<<:endl>

运行结果是:

C++Projects]$clang++-Wall-std=c++11main.cxx

[C++Projects]$./a.out

112233445567753245234

[C++Projects]$

使用命名的迭代器与通常的

STL

程序猿的风格相违背

,

但是为了使代码对所有的编译器都没有二义性

,

并且使维护代码的人理解起来更容易

,

这一代价是值得的

.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值