2

我遇到了一个稍微不寻常的问题。考虑以下代码:

class parser
{
    lexer lex;

public:

    node_ptr parse(const std::string& expression)
    {
        lex.init(expression.begin(), expression.end());
        // ...
        // call some helper methods
        // return the result
    }

private:

    // lots of small helper methods, many of them accessing lex
};

parse 方法用一个方法初始化词法分析器init。在此之前,词法分析器处于不可用的“默认”状态。通常,在构造过程中应该初始化一个成员,所以我为什么不简单地这样做:

class parser
{
    lexer lex;

public:

    parser(const std::string& expr) : lex(expr.begin(), expr.end()) {}

    node_ptr parse()
    {
        // call some helper methods
        // return the result
    }
    // ...
};

首先,这意味着客户端可以多次调用 parse 方法,这没有多大意义。

其次,更重要的是,它很容易导致严重的生命周期问题:

parser my_parser("1 * 2 + 3 * 4");
auto root = my_parser.parse();

在上面的代码中,词法分析器将使用一个在行尾不再存在的临时字符串对象进行初始化,因此parse在下一行调用该方法将调用未定义的行为。

由于这两个原因,我真的很想用相同的方法进行初始化和解析。不幸的是,我不能在构造函数中这样做,因为我需要返回一个结果,而构造函数不能返回结果。

从技术上讲,如果我也相应地更改构造函数和析构函数,则可以在方法内部构造词法分析器parse并在之后将其销毁:

class parser
{
    static std::string dummy;
    lexer lex;

public:

    parser() : lex(dummy.begin(), dummy.end())
    {
        lex.~lexer();
    }

    node_ptr parse(const std::string& expression)
    {
        new(&lex) lexer(expression.begin(), expression.end());
        // call some helper methods
        lex.~lexer();
        // return the result
    }

    ~parser()
    {
        new(&lex) lexer(dummy.begin(), dummy.end());
    }
    // ...
};

但这是迄今为止我在很长一段时间内编写的最丑陋的代码。它也不是异常安全的;如果辅助方法抛出怎么办?事实上,这正是遇到解析错误时发生的情况。

那么我应该如何解决这个问题呢?在内部使用本地词法分析器parse并有一个lexer*成员指向它?使用boost::optional<lexer>会员?还是我应该只接受这种init方法?还是我应该在构造函数中进行解析并抛出一个包含所需结果的“期望”?

4

4 回答 4

3

我绝对不会做你的第二个例子。更好的是构造lexerin Parse(),并存储一个指针 or boost::optional。但是如果你想允许这样做,那么你的辅助函数必须在继续之前检查词法分析器是否有效。对我来说似乎很乱。

更好的是只做Parse一个独立的功能。我设想这对呼叫者更明智,并解决您的问题:

void parser_helper(lexer& lex)
{
    ...
}

node_ptr Parse(const std::string& inp)
{
    lexer lex(inp);
    ...
    parser_helper(lex);
    ...
    return ret;
}

或者如果你有更多的状态要传递......

class parser_helper
{
    lexer lex;
    ... other state here

public:
    parser_helper(const std::string& inp) :
        lex(inp)
    {
    }

    ... helper functions here.
    void helper_function() { }
}

node_ptr Parse(const std::string& inp)
{
    parser_helper helper(inp);
    ...
    helper.helper_function();
    ...
    return ret;
}

无论哪种方式,词法分析器都应该只是Parse函数中的一个自动变量。

这个想法是调用者期望的接口只是一个函数。不需要让调用者处理一个类,只是因为它的内部Parse有状态/帮助函数。

于 2012-07-12T16:03:41.960 回答
2

您没有说明为什么parse(实际上,lex)不应该是简单的函数。此外,您没有理由parse采用表达式或采用初始化的lexer.

编辑:

或者简单地将它们创建为解析堆栈上的 lambda。但是,正如我所说,我可以看到解析器存在的必要性。但它似乎不需要存在于 parse 本身的成员方法之外,质疑为什么不重构该方法以走出类。就像是

class parser { 
    lexer l;
     // stuff
};
node_ptr parse(...) { 
    parser p(...); 
    return p(); 
}
于 2012-07-12T16:02:46.190 回答
0

在上面的代码中,词法分析器将使用一个在行尾不再存在的临时字符串对象进行初始化,因此在下一行调用 parse 方法将调用未定义的行为。

那没有意义。您可以制作临时字符串的副本并在lexer.

于 2012-07-12T16:05:13.987 回答
0

在 parse 中使用本地词法分析器并让 lexer* 成员指向它?

这得到了我的投票。这样你就可以完全控制它的生命周期。

于 2012-07-12T16:07:42.177 回答