我正在用 C++ 编写一个简单的解析器,旨在解析 s-表达式语言的子集。
我正在尝试以干净的方式为标记生成器设计继承层次结构,但我遇到了对象切片问题,因为我一直在努力避免动态分配。我想避免动态分配的原因是为了避免在我的分词器和解析器中引入内存泄漏的问题。
整体结构是:一个Parser有一个Tokenizer实例。解析器调用 Tokenizer::peek(),它返回输入头部的标记。我希望 peek() 按值返回 Token 实例,而不是动态分配正确的派生类的 Token 并返回指针。
更具体地说,假设有两种 token 类型:Int 和 Float。这是一个有望澄清问题的示例:
class Token {
public:
virtual std::string str() { return "default"; }
};
template <typename T>
class BaseToken : public Token {
public:
T value;
BaseToken(const T &t) : value(t) {}
virtual std::string str() {
return to_str(value);
}
};
class TokenInt : public BaseToken<int> {
public:
TokenInt(int i) : BaseToken(i) {}
};
class TokenFloat : public BaseToken<float> {
TokenFloat(float f) : BaseToken(f) {}
};
Token peek() {
return TokenInt(10);
}
int main() {
Token t = peek();
std::cout << "Token is: " << t.str() << "\n";
return 0;
}
很明显,输出是“Token is: default”而不是“Token is: 10”,因为 TokenInt 被切片为 Token。
我的问题是:是否有适当的继承结构或设计模式来完成这种类型的多态性而不使用动态分配?
最佳答案
因此,扩展我的评论,您可以使用 boost::variant。文档有一个很好的教程( http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html ),但这里有一个如何在您的情况下使用它的示例(注意 - 我添加了一些功能来展示如何使用极其方便的 static_visitor)
Boost::variant 也是仅 header 的,因此在链接时不需要特别注意。
(注意 - 您可以直接使用 boost::variant 作为您的 Token 类型;但是,如果您将其封装在类中,则可以在类方法中隐藏访问者的使用)
#include <string>
#include <sstream>
#include <boost/variant.hpp>
typedef boost::variant<std::string, int, float> TokenData;
// Define a function overloaded on the different variant contained types:
std::string type_string(int i)
{
return "Integer";
}
std::string type_string(std::string const& s)
{
return "String";
}
std::string type_string(float f)
{
return "Float";
}
// Visitors implement type specific behavior. See the boost::variant docs
// for some more interesting visitors (recursive, multiple dispatch, etc)
class TypeVisitor : public boost::static_visitor<std::string> {
public:
template <typename T>
std::string operator()(T const& val) const
{
return type_string(val);
}
};
// Token class - no inheritance, so no possible slicing!
class Token {
public:
template <typename T>
Token(const T& value):
m_value(value)
{}
std::string str() const {
// Variants by default have their stream operators defined to act
// on the contained type. You might want to just define operator<<
// for the Token class (see below), but I'm copying your method
// signature here.
std::stringstream sstr;
sstr << m_value;
return sstr.str();
}
std::string token_type() const {
// Note: you can actually just use m_value.type() to get the type_info for
// the variant's type and do a lookup based on that; however, this shows how
// to use a static_visitor to do different things based on type
return boost::apply_visitor(TypeVisitor(), m_value);
}
private:
TokenData m_value;
friend
std::ostream& operator<<(std::ostream&, Token const&);
};
// An alternative to the "str" method
std::ostream& operator<<(std::ostream& oo, Token const& tok)
{
return oo << tok.m_value;
}
int main(){
Token t1(10), t2("Hello"), t3(1.5f);
std::cout << "Token 1 is: " << t1.str() << " Type: "<< t1.token_type() << "\n";
std::cout << "Token 2 is: " << t2.str() << " Type: "<< t2.token_type() << "\n";
// Use Token::operator<< instead:
std::cout << "Token 3 is: " << t3 << " Type: "<< t3.token_type() << "\n";
}
输出:
Token 1 is: 10 Type: Integer
Token 2 is: Hello Type: String
Token 3 is: 1.3 Type: Float
关于c++ - C++编写的分词器的继承设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28438768/