少し変わった問題に遭遇しました。次のコードを検討してください。
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());
}
// ...
};
しかし、これは私が非常に長い間書いた中で最も醜いコードです。また、例外セーフではありません。ヘルパー メソッドがスローするとどうなりますか? 実際、解析エラーが発生した場合は、まさにこれが発生します。
では、この問題をどのように解決すればよいでしょうか。内部でローカル レクサーを使用しparse
、lexer*
メンバーがそれを指すようにしますか? boost::optional<lexer>
メンバーを使用しますか?それとも、私はそのinit
方法で生きるべきですか?それとも、結局コンストラクターで解析を行い、目的の結果を含む「期待」をスローする必要がありますか?