我正在用C ++写一个简单的解析器,旨在解析s表达式语言的子集。
我正在尝试以一种干净的方式为令牌生成器设计继承层次结构,但是由于我一直在尝试避免动态分配,因此我遇到了对象切片问题。我想避免动态分配的原因是避免在令牌生成器和解析器中引入内存泄漏的问题。
总体结构为:解析器具有Tokenizer实例。解析器调用Tokenizer :: peek(),该令牌返回输入开头的令牌。我希望peek()按值返回Token实例,而不是动态分配正确派生类的Token并返回指针。
更具体地说,假设有两种令牌类型:Int和Float。这是一个示例,希望可以解决此问题:
class Token {
public:
virtual std::string str() { return"default"; }
};
template
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 {
public:
TokenInt(int i) : BaseToken(i) {}
};
class TokenFloat : public BaseToken {
TokenFloat(float f) : BaseToken(f) {}
};
Token peek() {
return TokenInt(10);
}
int main() {
Token t = peek();
std::cout <
";
return 0;
}
很明显,由于将TokenInt切成一个Token,因此输出为" Token is:default"而不是" Token is:10"。
我的问题是:是否有适当的继承结构或设计模式可以在不使用动态分配的情况下实现这种类型的多态性?
将动态内存分配与智能指针一起使用。 动态内存分配将解决对象切片问题。 智能指针将解决内存管理问题。
因此,扩展我的评论,您可以使用boost :: variant。该文档上有一个很好的v教程(http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html),但这是在您的情况下如何使用它的示例(注意-我添加了一些功能来展示如何使用极为方便的static_visitor)
Boost :: variant也是仅标头的,因此链接时不需要特别注意。
(注意-您可以直接使用boost :: variant作为Token类型;但是,如果将其封装在类中,则可以将访问者的使用隐藏在类方法内)
#include
#include
#include
typedef boost::variant<: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<:string> {
public:
template
std::string operator()(T const& val) const
{
return type_string(val);
}
};
// Token class - no inheritance, so no possible slicing!
class Token {
public:
template
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<
};
// An alternative to the"str" method
std::ostream& operator<
{
return oo << tok.m_value;
}
int main(){
Token t1(10), t2("Hello"), t3(1.5f);
std::cout <
";
std::cout <
";
// Use Token::operator<< instead:
std::cout <
";
}
输出:
Token 1 is: 10 Type: Integer
Token 2 is: Hello Type: String
Token 3 is: 1.3 Type: Float
为了返回一个值,您必须知道它的大小。唯一的方法是:
返回注释中建议的指向基本令牌类型的智能指针,
返回所有您根据类型标签转换的令牌类型的并集,或者
返回包含类型标签和匹配文本的通用令牌类型
令人遗憾的数字2和3实在令人痛苦(#2因为联合是一种痛苦,#3因为抽象包含值的类型很痛苦),因为确实没有充分的理由分词器应该分配任何东西。
工会尤其是痛苦。 非POD类。 ID建议使用有区别的联合-特别是boost :: variant。 它基于堆栈,因此您可以避免任何动态分配问题,并且IMO非常易于使用。
@Anna,您应该写下一个答案。 您的想法对我来说似乎是一个胜利者。