从 antlrcpp::Any 中检索基数 class
Retrieving Base class from antlrcpp::Any
我目前正在将一些代码从 C# 版本的 ANTLR4 移植到 C++ 目标,我目前 运行 遇到了一些问题。我在 C# 中构建我的 AST 的方法是创建一个基础 class(我们称它为 Base)并派生 classes(我们称它为 Derived),我可以使用虚函数来实现所说的 classes。
但是尝试将此代码转换为 C++,我不断收到 bad_cast 异常
我已将其缩小到 antlrcpp::Any 没有正确地将派生的 class 转换为它的基础 class。例如:
class Base {
public:
virtual std::string ToString() const { return "Base\n"; }
};
class Derived : public Base {
public:
std::string ToString() const override { return "Derived\n"; }
};
int main() {
Derived value;
std::cout << value.ToString(); //Obviously prints out Derived
Base& base_value = value;
std::cout << base_value.ToString(); //Still works exactly as expected and prints Derived
auto any = antlrcpp::Any(value);
auto derived = any.as<Base>(); //internally uses a dynamic_cast and throws a bad_cast
std::cout << derived.ToString(); //never gets to here
}
我最初认为这可能是因为它只适用于指针,但是
auto any = antlrcpp::Any(new Derived());
std::cout << any.as<Base*>()->ToString(); //throws bad_cast
我将 dynamic_cast
更改为 header 内的 static_cast
,它会投射,但它会打印出 "Base"。当访问任何数据成员时,C-style 直接转换会导致崩溃。
我该如何使用 antlrcpp::Any
来获得一个 Base class?有什么明显的我遗漏的吗?
如果这不可能,我该如何解决这个问题?有 .is() 方法,但是在很多情况下,检查访问者的 return 值是否属于某种类型是不可行的(例如表达式,可以有 30-40 个运算符)。
antlrcpp::any
class 不适合这种情况。它不是通用的变体实现。
如果您需要,您应该考虑一个自己的 Variant 实现,它使用各种类型等的联合,这不适用于类型擦除。
一般而言,唯一指针在这种情况下可能不是一个好主意,因为它们不支持复制语义(您在这里需要它)。对于访问者的评估,最好使用 shared_ptr
。
我是这样工作的:
一些背景知识 - 我正在创建一个 whizzbang 优化 "Function Applier" a class 将其复杂功能应用于传入的数据记录。我不想每次都访问解析树时间我得到了一条新的数据记录,所以我创建了我的优化 whizzbang 树状对象来将该函数应用于数据。
现在这个对象的基础 class 和所有派生的 classes 将有一个 .apply(some_data) 函数,所以它们都派生自基础类型,但都执行调用 .apply() 时的不同功能,包括对属于它们的子对象调用 .apply() 。
我正在创建的最基本的函数应用对象return是一个常量数据结构,当传递一条数据记录时,它带有一个双精度表示其中的常量整数
所以:
applier_instance.apply(some_data)
returns:
{23.0, ...}
无论传递给它什么数据。传递它 {"CAT","DOG","FOO"}
它会 return {23.0, ...}
我希望在 antlr 解析器看到字符串“23”时创建这个 applier_instance 对象。
现在,如果你已经走到这一步,你会意识到我需要将这个对象传递给多个访问者,大部分时间这将通过一个 antlrcpp::Any
对象和多次调用来完成非常相似的默认访问者方法,例如以下 antlr 生成的访问者代码中的方法,它访问解析树的 'Expression' 节点:
virtual antlrcpp::Any visitP_expression(MyParser::P_expressionContext *ctx) override {
return visitChildren(ctx);
}
..终于脱离了我的 'start' 规则...
virtual antlrcpp::Any visitStart(MyParser::StartContext *ctx) override {
return visitChildren(ctx);
}
就像你一样,我 运行 遇到了一个问题,试图通过默认函数向上移动唯一指针。
我的答案是在我的访问者函数中执行此操作以处理解析树上的整数文字:
antlrcpp::Any visitP_NUMBER(MyParser::P_NUMBERContext *ctx) {
IFunctionApplier* fa = new IntegerLiteralApplier(stoi(ctx->getText()));
return fa;
}
我创建了指向 IFunctionApplier 接口(定义了虚拟 .apply(some_data) 方法)的原始指针,并创建了一个继承自该接口的新 IntegerLiteralApplier 对象。
我听到你问的上一个代码块中 new 的 delete 在哪里?
没有 - 我把我的原始指针变成一个唯一的指针,一旦它从 antlr 生成的函数调用的顶部弹出:
...
AttributeMapParser parser(&tokens);
//Get 'start' rule of the parser to make a parse tree (Abstract Syntax Tree)
AttributeMapParser::StartContext* tree = parser.start();
////Walk and visit the tree returning a raw pointer to a function applier
auto fa_raw = visitor.visitStart(tree).as<IFunctionApplier*>();
//convert pointer fa_raw to a unique pointer
auto fa = std::unique_ptr<IFunctionApplier>(fa_raw);
//Clear up the parser and tree
parser.reset();
//start using the unique pointer
auto result = fa->apply(some_input_data);
现在我警告你,我是 c++ 的初学者,移植了 Python 鸭式 .apply(some_data) 方法的代码,所以请谨慎阅读我的回答,但是
- 有效
- 一个有 25 年 c++ 经验的人后来看了我一眼,说它看起来,我引用,"OK"
- 网上antlr c++ runtime的资源很少,所以我想分享一下我的经验
祝你好运!
p.s。编辑 2019/11/13
为了展示如何,当我沿着树向上移动时,我会使用在树底部创建的指针:
//For 'not' atom
virtual antlrcpp::Any visitAtom_not(MyParser::Atom_notContext *ctx) override {
auto raw_exp = visit(ctx->atm).as<IFunctionApplier*>();
auto unique_exp = std::unique_ptr<IFunctionApplier>(raw_exp);
IFunctionApplier* fa = new NotApplier(std::move(unique_exp));
return fa;
}
看看我如何将 antlrcpp::Any
转换为指向接口 IFunctionApplier 的原始指针,然后将其转换为唯一指针并将其移动到我的父对象中(所以这里是一个文字整数 'functionapplier'被传递到 'not' 'applier',并成为一个唯一的指针。
原始指针 fa
来自访问 return,并将传递给下一个 ctx
调用。
我目前正在将一些代码从 C# 版本的 ANTLR4 移植到 C++ 目标,我目前 运行 遇到了一些问题。我在 C# 中构建我的 AST 的方法是创建一个基础 class(我们称它为 Base)并派生 classes(我们称它为 Derived),我可以使用虚函数来实现所说的 classes。
但是尝试将此代码转换为 C++,我不断收到 bad_cast 异常 我已将其缩小到 antlrcpp::Any 没有正确地将派生的 class 转换为它的基础 class。例如:
class Base {
public:
virtual std::string ToString() const { return "Base\n"; }
};
class Derived : public Base {
public:
std::string ToString() const override { return "Derived\n"; }
};
int main() {
Derived value;
std::cout << value.ToString(); //Obviously prints out Derived
Base& base_value = value;
std::cout << base_value.ToString(); //Still works exactly as expected and prints Derived
auto any = antlrcpp::Any(value);
auto derived = any.as<Base>(); //internally uses a dynamic_cast and throws a bad_cast
std::cout << derived.ToString(); //never gets to here
}
我最初认为这可能是因为它只适用于指针,但是
auto any = antlrcpp::Any(new Derived());
std::cout << any.as<Base*>()->ToString(); //throws bad_cast
我将 dynamic_cast
更改为 header 内的 static_cast
,它会投射,但它会打印出 "Base"。当访问任何数据成员时,C-style 直接转换会导致崩溃。
我该如何使用 antlrcpp::Any
来获得一个 Base class?有什么明显的我遗漏的吗?
如果这不可能,我该如何解决这个问题?有 .is() 方法,但是在很多情况下,检查访问者的 return 值是否属于某种类型是不可行的(例如表达式,可以有 30-40 个运算符)。
antlrcpp::any
class 不适合这种情况。它不是通用的变体实现。
如果您需要,您应该考虑一个自己的 Variant 实现,它使用各种类型等的联合,这不适用于类型擦除。
一般而言,唯一指针在这种情况下可能不是一个好主意,因为它们不支持复制语义(您在这里需要它)。对于访问者的评估,最好使用 shared_ptr
。
我是这样工作的:
一些背景知识 - 我正在创建一个 whizzbang 优化 "Function Applier" a class 将其复杂功能应用于传入的数据记录。我不想每次都访问解析树时间我得到了一条新的数据记录,所以我创建了我的优化 whizzbang 树状对象来将该函数应用于数据。
现在这个对象的基础 class 和所有派生的 classes 将有一个 .apply(some_data) 函数,所以它们都派生自基础类型,但都执行调用 .apply() 时的不同功能,包括对属于它们的子对象调用 .apply() 。
我正在创建的最基本的函数应用对象return是一个常量数据结构,当传递一条数据记录时,它带有一个双精度表示其中的常量整数 所以:
applier_instance.apply(some_data)
returns:
{23.0, ...}
无论传递给它什么数据。传递它 {"CAT","DOG","FOO"}
它会 return {23.0, ...}
我希望在 antlr 解析器看到字符串“23”时创建这个 applier_instance 对象。
现在,如果你已经走到这一步,你会意识到我需要将这个对象传递给多个访问者,大部分时间这将通过一个 antlrcpp::Any
对象和多次调用来完成非常相似的默认访问者方法,例如以下 antlr 生成的访问者代码中的方法,它访问解析树的 'Expression' 节点:
virtual antlrcpp::Any visitP_expression(MyParser::P_expressionContext *ctx) override {
return visitChildren(ctx);
}
..终于脱离了我的 'start' 规则...
virtual antlrcpp::Any visitStart(MyParser::StartContext *ctx) override {
return visitChildren(ctx);
}
就像你一样,我 运行 遇到了一个问题,试图通过默认函数向上移动唯一指针。
我的答案是在我的访问者函数中执行此操作以处理解析树上的整数文字:
antlrcpp::Any visitP_NUMBER(MyParser::P_NUMBERContext *ctx) {
IFunctionApplier* fa = new IntegerLiteralApplier(stoi(ctx->getText()));
return fa;
}
我创建了指向 IFunctionApplier 接口(定义了虚拟 .apply(some_data) 方法)的原始指针,并创建了一个继承自该接口的新 IntegerLiteralApplier 对象。
我听到你问的上一个代码块中 new 的 delete 在哪里?
没有 - 我把我的原始指针变成一个唯一的指针,一旦它从 antlr 生成的函数调用的顶部弹出:
...
AttributeMapParser parser(&tokens);
//Get 'start' rule of the parser to make a parse tree (Abstract Syntax Tree)
AttributeMapParser::StartContext* tree = parser.start();
////Walk and visit the tree returning a raw pointer to a function applier
auto fa_raw = visitor.visitStart(tree).as<IFunctionApplier*>();
//convert pointer fa_raw to a unique pointer
auto fa = std::unique_ptr<IFunctionApplier>(fa_raw);
//Clear up the parser and tree
parser.reset();
//start using the unique pointer
auto result = fa->apply(some_input_data);
现在我警告你,我是 c++ 的初学者,移植了 Python 鸭式 .apply(some_data) 方法的代码,所以请谨慎阅读我的回答,但是
- 有效
- 一个有 25 年 c++ 经验的人后来看了我一眼,说它看起来,我引用,"OK"
- 网上antlr c++ runtime的资源很少,所以我想分享一下我的经验
祝你好运!
p.s。编辑 2019/11/13
为了展示如何,当我沿着树向上移动时,我会使用在树底部创建的指针:
//For 'not' atom
virtual antlrcpp::Any visitAtom_not(MyParser::Atom_notContext *ctx) override {
auto raw_exp = visit(ctx->atm).as<IFunctionApplier*>();
auto unique_exp = std::unique_ptr<IFunctionApplier>(raw_exp);
IFunctionApplier* fa = new NotApplier(std::move(unique_exp));
return fa;
}
看看我如何将 antlrcpp::Any
转换为指向接口 IFunctionApplier 的原始指针,然后将其转换为唯一指针并将其移动到我的父对象中(所以这里是一个文字整数 'functionapplier'被传递到 'not' 'applier',并成为一个唯一的指针。
原始指针 fa
来自访问 return,并将传递给下一个 ctx
调用。