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 メソッドを複数回呼び出すことができることを意味しますが、これはあまり意味がありません。

2 つ目は、さらに重要なことですが、生涯にわたって重大な問題を簡単に引き起こす可能性があります。

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());
    }
    // ...
};

しかし、これは私が非常に長い間書いた中で最も醜いコードです。また、例外セーフではありません。ヘルパー メソッドがスローするとどうなりますか? 実際、解析エラーが発生した場合は、まさにこれが発生します。

では、この問題をどのように解決すればよいでしょうか。内部でローカル レクサーを使用しparselexer*メンバーがそれを指すようにしますか? boost::optional<lexer>メンバーを使用しますか?それとも、私はそのinit方法で生きるべきですか?それとも、結局コンストラクターで解析を行い、目的の結果を含む「期待」をスローする必要がありますか?

4

4 に答える 4

3

私は間違いなくあなたの2番目の例をしません。lexerinを構築しParse()、ポインターまたはを格納する方がよいでしょう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

編集:

または、解析のスタック上にラムダとしてそれらを作成するだけです。ただし、前述したように、パーサーが存在する必要があることがわかります。しかし、それ自体を解析するメンバーメソッドの外に存在する必要はないようです。なぜそのメソッドをリファクタリングしてクラスの外に出さないのか疑問に思っています。何かのようなもの

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 を使用し、lexer* メンバーがそれを指すようにしますか?

それは私の投票を取得します。そうすれば、その寿命を完全に制御できます。

于 2012-07-12T16:07:42.177 に答える