C++ 是否将取消引用的指针作为函数未定义行为的引用返回?
这是我第一次编写解析器。我跟在后面。我要让它工作,但我遇到了一个问题 最初的教程是用Java编写的。我更喜欢C++,这就是我写的。我基本上能够将大部分代码移植到C++(虽然我确实把它变成了“我的”),因为它有一些非语言相关的差异。我唯一真正的问题是这一行代码:C++ 是否将取消引用的指针作为函数未定义行为的引用返回?,c++,language-lawyer,C++,Language Lawyer,这是我第一次编写解析器。我跟在后面。我要让它工作,但我遇到了一个问题 最初的教程是用Java编写的。我更喜欢C++,这就是我写的。我基本上能够将大部分代码移植到C++(虽然我确实把它变成了“我的”),因为它有一些非语言相关的差异。我唯一真正的问题是这一行代码: 这在java中很好(我假设。我以前从来没有真正用java工作过,但是我假设这个家伙知道他在做什么),但是C++里没有这么多。通过使用如下指针,我能够完成同样的事情: Expression* parse(Parser& parser
Expression* parse(Parser& parser, Token token) {
Expression* operand = parser.parseExpression();
return new PrefixExpression(token.getType(), operand);
(尽管我不熟悉java的语义)在C++中似乎完全相同,只使用指针而不是普通对象。
然而,像这样使用指针的问题是,它很快就会变得一团糟。现在使用指针变得容易多了,这意味着我必须担心释放,如果我做得不对,可能会导致内存泄漏。它只是变得一团糟 现在,解决方案似乎很简单。我可以像这样返回PrefixExpression
:
Expression parse(Parser& parser, Token token) {
Expression operand = parser.parseExpression();
return PrefixExpression(token.getType(), operand);
Grammar g;
g.RegisterPrefix("(", &ParenPrefixParselet);
这是我的问题:如果我这样做,我会丢失vtable和这个新的表达式中的任何额外数据。这是一个问题,因为Expression
实际上只是许多类型表达式的基类Parse
可以解析它想要解析的任何内容,而不仅仅是前缀表达式
。原作就是这样设计的。一般来说,我喜欢这种设计,但是,正如你所看到的,它会带来问题。只要在这里返回一个新的表达式
,我以后就无法从该对象获得所需的东西
现在,我可以通过返回一个引用来解决这个问题:
Expression& parse(Parser& parser, Token token) {
// ...
return PrefixExpression(token.getType(), operand);
这解决了vtable和额外数据的问题,但现在这就产生了一个新的问题。我返回一个变量的引用,该变量将立即被销毁,这没有任何帮助
所有这些都是为了说明,这就是我最初最终选择指针的原因。指针可以让我保留以后需要的数据,但它们确实很难使用。我可以挤过去,但就我个人而言,我想要更好的
我想我可以使用std::move
,但我对这一点还不太熟悉,无法确定我会正确使用它。如果有必要的话,我会的,但是正确地实现这一点需要一些我没有的技能和知识。除此之外,这是一个很大的工作重做一切,我必须工作到这一点
所有这些都引出了我问题的要点:我能简单地安全地返回对新对象的引用吗?让我举个例子:
Expression& parse(Parser& parser, Token token) {
//...
return *(new PrefixExpression(token.getType(), operand));
这将很好地解决我的大部分问题,因为如果它做了我认为它做的事情,我会得到一个新对象的引用,保留vtable和额外的数据,并且不会立即被销毁。这样我就可以吃蛋糕了
然而,我的问题是,我真的可以这样做吗?虽然我觉得我有很好的理由这么做,但这对我来说似乎很奇怪。我在函数内部分配新数据,并期望它像任何普通变量一样在函数外部自动释放。即使它真的起作用了,它是否会像我所期望的那样完全超出这个功能?我担心这可能会调用未定义的行为或类似的东西。标准对此有何看法
编辑:这里是一个请求的最小样本:
表达方式:
// A (not really pure) purely virtual base class that holds all types of expressions
class Expression {
protected:
const std::string type;
public:
Expression() : type("default") {}
virtual ~Expression() {} //Because I'm dealing with pointers, I *think* I need a virtual destructor here. Otherwise, I don't really need
virtual operator std::string() {
// Since I am working with a parser, I want some way to debug and make sure I'm parsing correctly. This was the easiest.
throw ("ERROR: No conversion to std::string implemented for this expression!");
}
// Keep in mind, I may do several other things here, depending on how I want to use Expression
};
子表达式
,用于括号:
class Paren : public Expression {
private:
// Again, Pointer is not my preferred way, but this was just easier, since Parse() was returning a pointer anyway.
Expression* value;
public:
Paren(Expression *e) {
// I know this is also sketchy. I should be trying to perform a copy here.
// However, I'm not sure how to do this, since Expression could be anything.
// I just decided to write my code so the new object takes ownership of the pointer. I could and should do better
value = e;
}
virtual operator std::string() {
return "(" + std::string(*value) + ")";
}
// Because again, I'm working with pointers
~Paren() {delete value;}
};
和解析器:
class Parser {
private:
Grammar::Grammar grammar;
public:
// this is just a function that creates a unique identifier for each token.
// Tokens normally have types identifier, number, or symbol.
// This would work, except I'd like to make grammar rules based off
// the type of symbol, not all symbols in general
std::string GetMapKey(Tokenizer::Token token) {
if(token.type == "symbol") return token.value;
return token.type;
}
// the parsing function
Expression * parseExpression(double precedence = 0) {
// the current token
Token token = consume();
// detect and throw an error here if we have no such prefix
if(!grammar.HasPrefix(GetMapKey(token))) {
throw("Error! Invalid grammar! No such prefix operator.");
}
// get a prefix parselet
Grammar::PrefixCallback preParse = grammar.GetPrefixCallback(GetMapKey(token));
// get the left side
Expression * left = preParse(token,*this);
token = peek();
double debug = peekPrecedence();
while(precedence < peekPrecedence() && grammar.HasInfix(GetMapKey(token))) {
// we peeked the token, now we should consume it, now that we know there are no errors
token = consume();
// get the infix parser
Grammar::InfixCallback inParse = grammar.GetInfixCallback(GetMapKey(token));
// and get the in-parsed token
left = inParse(token,left,*this);
}
return left;
}
这让我可以写一个语法,在括号中可以这样写:
Expression parse(Parser& parser, Token token) {
Expression operand = parser.parseExpression();
return PrefixExpression(token.getType(), operand);
Grammar g;
g.RegisterPrefix("(", &ParenPrefixParselet);
最后,一个main()
intmain(){
语法g;
g、 RegisterPrefix(“(”,和ParenPrefixParselet);
解析器(g);
表达式*e=parser.parseExpression(0);
std::cout您希望使用多态性-有两种方法。要么使用引用,要么使用指针。引用的问题是,当您返回它们时是危险的。大多数情况下,当您返回对本地对象的引用时。这意味着我们只剩下指针
但是不要使用new
和delete
。它们不安全,很难处理,尤其是在多作用域环境中。使用智能指针。使用唯一\u ptr
:
#include <memory>
struct expression {
virtual void foo() = 0;
virtual ~expression() = default;
};
struct prefix_expression : expression {
virtual void foo() { /* default impl */ }
// dummy c-tor
prefix_expression(int) {}
};
// note that parse() returns a pointer to any *expression*!
std::unique_ptr<expression> parse() {
// pass to make_unique whatever arguments the constructor of prefix_expression needs
return std::make_unique<prefix_expression>(42);
}
int main() {
{
auto expr = parse();
// here, *expr* goes out of score and properly deletes whatever it has new-ed
}
}
#包括
结构表达式{
虚拟void foo()=0;
virtual~expression()=默认值;
};
结构前缀\表达式:表达式{
虚拟void foo(){/*默认impl*/}
//虚拟c-tor
前缀_表达式(int){}
};
//请注意,parse()返回指向任意*表达式*的指针!
std::unique_ptr parse(){
//传递以使_唯一前缀表达式的构造函数所需的任何参数
返回标准::使_唯一(42);
}
int main(){
{
auto expr=parse();
//在这里,*expr*超出了评分范围,并正确地删除了它新编辑的内容
}
}
编辑:
要回答标题中的问题-否你是对的-你需要一个指针,要绕过范围,你需要动态分配
Java已经在幕后为您这样做了
不过,不要使用new
,要使用智能指针,以免弄脏
我们不能提供“标准的报价”关于这一点,因为我们必须引用20或30页的规则,从自动存储持续时间如何工作,到解引用如何工作,到左值如何工作,到复制如何工作,到继承如何工作,到虚拟成员函数如何工作等等。由于使用new,然后丢弃指针,因此不可能删除该对象。但我的第一印象不是UB,只是内存泄漏。我的第一本能是返回一个对象,让C++来处理它。“返回PyFieldExchange(令牌。GETType()),“操作数”,“这是我的问题:如果我这样做,我就失去了VTABLE和A。
int main() {
Grammar g;
g.RegisterPrefix("(", &ParenPrefixParselet);
Parser parser(g);
Expression* e = parser.parseExpression(0);
std::cout << static_cast<std::string>(*e);
return 0;
}
#include <memory>
struct expression {
virtual void foo() = 0;
virtual ~expression() = default;
};
struct prefix_expression : expression {
virtual void foo() { /* default impl */ }
// dummy c-tor
prefix_expression(int) {}
};
// note that parse() returns a pointer to any *expression*!
std::unique_ptr<expression> parse() {
// pass to make_unique whatever arguments the constructor of prefix_expression needs
return std::make_unique<prefix_expression>(42);
}
int main() {
{
auto expr = parse();
// here, *expr* goes out of score and properly deletes whatever it has new-ed
}
}