常见错误14:捉摸不定的评估求值次序
再没有比模糊的软件工程师设下的评估求值次序陷阱更能发现C++语言的C语言渊源印记了。
C语言和C++语言在表达式如何评估求值的问题上留下了很大的处理余地。这种灵活性能够使得
编译器生成高度优化的可执行代码,但同时也要求软件工程师更仔细地审视社稷这个问题的源代码,
以防止对评估求值次序作出任何无依据,先入为主的假设。
子表达式的评估求值顺序不固定
运算符的优先级和结合性对评估求值次序没有影响
定位new的评估求值次序
将评估求值次序固定下来的运算符
再没有比模糊的软件工程师设下的评估求值次序陷阱更能发现C++语言的C语言渊源印记了。
C语言和C++语言在表达式如何评估求值的问题上留下了很大的处理余地。这种灵活性能够使得
编译器生成高度优化的可执行代码,但同时也要求软件工程师更仔细地审视社稷这个问题的源代码,
以防止对评估求值次序作出任何无依据,先入为主的假设。
子表达式的评估求值顺序不固定
运算符的优先级和结合性对评估求值次序没有影响
定位new的评估求值次序
将评估求值次序固定下来的运算符
不当的运算符重载 对于C++语言来说,运算符重载只是“语法糖”
e.cpp
#include "e.h"
E::E()
{}
E::~E()
{}
Plus::Plus( E *l, E *r )
: l_(l), r_(r) {}
Plus::~Plus()
{ delete r_; delete l_; }
// incorrect implementation below!
//int Plus::eval() const
// { return l_->eval() + r_->eval(); }
// correct implementation
int
Plus::eval() const {
int tmp = l_->eval();
return tmp + r_->eval();
}
Times::Times( E *l, E *r )
: l_(l), r_(r) {}
Times::~Times()
{ delete r_; delete l_; }
int
Times::eval() const {
int tmp = l_->eval();
return tmp * r_->eval();
}
Int::Int( int value )
: v_( value ) {}
int
Int::eval() const
{ return v_; }
Var::Var( const std::string &id )
: id_( id ) {}
int
Var::eval() const
{ return stab_[id_]; }
int
Var::set( int newValue )
{ return stab_[id_] = newValue; }
std::map<std::string,int> Var::stab_;
Assign::Assign( Var *var, E *expr )
: var_( var ), e_( expr ) {}
Assign::~Assign()
{ delete e_; delete var_; }
int
Assign::eval() const
{ return var_->set( e_->eval() ); }
Uminus::Uminus( E *e )
: e_( e ) {}
Uminus::~Uminus()
{ delete e_; }
int
Uminus::eval() const
{ return -e_->eval(); }
e.h
#ifndef E_H
#define E_H
/*
// Grammar for constructing the hierarchy:
E -> E + E
E -> E * E
E -> int
E -> id
E -> id = E
*/
#include <string>
#include <map>
class E {
public:
E();
virtual ~E();
virtual int eval() const = 0;
private:
E( const E & );
E &operator =( const E & );
};
class Plus : public E {
public:
Plus( E *l, E *r );
~Plus();
int eval() const;
private:
E *l_, *r_;
};
class Times : public E {
public:
Times( E *l, E *r );
~Times();
int eval() const;
private:
E *l_, *r_;
};
class Int : public E {
public:
Int( int value );
int eval() const;
private:
int v_;
};
class Var : public E {
public:
Var( const std::string &id );
int eval() const;
int set( int newValue );
private:
std::string id_;
static std::map<std::string,int> stab_;
};
class Assign : public E {
public:
Assign( Var *var, E *expr );
~Assign();
int eval() const;
private:
Var *var_;
E *e_;
};
class Uminus : public E {
public:
Uminus( E *e );
~Uminus();
int eval() const;
private:
E *e_;
};
#endif
eparse.cpp
#include <cstring>
#include <cctype>
#include "e.h"
#include "eparse.h"
using namespace std;
/*
Scanner: return the next token in the input stream.
attributes for integer constants and identifiers
available until next invocation
*/
void
ExprLex::scan() {
int c;
while( true )
switch( c = nextchar() ) {
case '+': case '-':
case '*': case '/':
case '(': case ')':
case '=':
tok = c;
return;
case ' ': case '\t':
continue;
case '\n':
tok = EOLN;
return;
default:
if( isdigit( c ) ) {
char *s = str;
do
*s++ = c;
while( isdigit( c = nextchar() ) );
*s = '\0';
unnextchar( c );
tok = INT;
return;
}
if( isalpha( c ) ) {
char *s = str;
do
*s++ = c;
while( isalnum(c = nextchar()) );
*s = '\0';
unnextchar( c );
tok = ID;
return;
}
tok = BAD;
return;
}
}
E *
ExprParser::parse() {
scan();
E *ast = s();
if( token() != EOLN )
error();
return ast;
}
void
ExprParser::error() {
throw std::range_error( "syntax error" );
}
E *
ExprParser::s() {
E *root = 0;
if( token() != EOLN )
root = e();
return root;
}
E *
ExprParser::e() {
E *root = t();
while( true )
switch( token() ) {
case '+':
scan();
root = new Plus( root, t() );
break;
case '-':
// uncomment out if allowing binary minus
//scan();
//root = new Minus( root, t() );
error(); // comment out if allowing binary minus
break;
default:
return root;
}
}
E *
ExprParser::f() {
E *root;
switch( token() ) {
case ID:
root = new Var( lexeme() );
scan();
if( token() == '=' ) {
scan();
root = new Assign( static_cast<Var *>(root), e() );
}
return root;
case INT:
root = new Int( atoi( lexeme() ) );
scan();
return root;
case '(':
scan();
root = e();
if( token() != ')' )
error();
scan();
return root;
case '-':
// uncomment out if allowing unary minus
scan();
return new Uminus( f() );
default:
error();
}
return 0; // will never execute, but some compilers
// complain if not there
}
E *
ExprParser::t() {
E *root = f();
while( true )
switch( token() ) {
case '*':
scan();
root = new Times( root, f() );
break;
case '/':
// uncomment out if allowing binary /
//scan();
//root = new Div( root, f() );
error(); // remove if parsing binary /
break;
default:
return root;
}
}
eparse.h
#ifndef EPARSE_H
#define EPARSE_H
#include "e.h"
#include <cstring>
class Lex { // generic lexical analyser interface
public:
Lex( std::size_t lexeme_size = 81 )
: str( new char[ lexeme_size ] ) {}
virtual ~Lex()
{ delete [] str; }
virtual void scan() = 0;
int token() const
{ return tok; }
const char *lexeme() const
{ return str; }
protected:
int tok;
char *str;
};
class CharLex : public Lex { // reads char stream
protected:
// unless overridden, reads from stdin
virtual int nextchar()
{ return getchar(); }
virtual void unnextchar( char c )
{ ungetc( c, stdin ); }
};
class ExprLex : public CharLex { // lex for exprs
public:
void scan();
};
enum { ID = 257, INT, EOLN, BAD };
class Parser { // generic parser interface
public:
Parser( Lex *lp )
: lex( lp ) {}
virtual ~Parser()
{ delete lex; }
virtual E *parse() = 0;
protected:
void scan()
{ lex->scan(); }
int token() const
{ return lex->token(); }
const char *lexeme() const
{ return lex->lexeme(); }
private:
Lex *lex;
};
/*
Predictive parser for a simple expression grammar:
S --> E eoln
E --> T {(+|-)T}
T --> F {(*|/)F}
F --> id | int | ( E ) | ID = E | -F
*/
class ExprParser : public Parser {
public :
ExprParser( Lex *lp )
: Parser( lp ) {}
~ExprParser()
{}
E *parse();
private:
E *s();
E *e();
E *t();
E *f();
void error();
};
#endif
main.cpp
#include <iostream>
#include <memory>
#include "e.h"
#include "eparse.h"
using namespace std;
int
main() {
Parser *parser = 0;
try {
parser = new ExprParser( new ExprLex );
while( true ) {
cout << "Enter an expression: " << flush;
auto_ptr<E> root( parser->parse() );
if( !root.get() )
break;
cout << "Result: " << root->eval() << endl;
getchar();
}
}
catch( const exception &e ) {
cout << "***" << e.what() << "***" << endl;
getchar();
}
catch( ... ) {
cout << "caught some exception" << endl;
getchar();
}
delete parser;
return 0;
}
输出 输入一个表达式出结果,要不然出异常,打印出异常
Enter an expression: 1+1
Result: 2