gpt4 book ai didi

c++ - 是否从函数未定义的行为返回取消引用的指针作为引用?

转载 作者:太空狗 更新时间:2023-10-29 20:01:36 25 4
gpt4 key购买 nike

我是第一次写解析器。我正在关注this tutorial on Pratt parers。我已经准备好了,但是我遇到了一个问题。

原始教程是用Java编写的。我更喜欢C++,所以这就是我写的。我基本上可以将大多数代码移植到C++(尽管我确实做到了“我的”,因为存在一些与语言无关的差异)。我唯一真正的问题是这段代码:

public Expression parse(Parser parser, Token token) {
Expression operand = parser.parseExpression();
? return new PrefixExpression(token.getType(), operand);


这在Java中很好用(我假设。我以前从未真正使用过Java,但我认为那家伙知道他在做什么),但在C++中则不是。我可以通过使用如下指针来完成同样的事情:
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);

这是我的问题:如果这样做,我将丢失vtable和此新 Expression中的所有额外数据。这是一个问题,因为 Expression实际上只是许多类型的表达式的基类。 Parse可以解析任何想要的内容,而不仅仅是 PrefixExpression。这就是原始设计的方式。通常,我喜欢这种设计,但是,正如您所看到的,它正在引起问题。仅在此处返回一个新的 Expression就会丢失我以后从该对象中获取的东西。

现在,我可以尝试通过返回引用来解决此问题:
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
};

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;
}


发布解析器代码后,我意识到我应该提到我将所有与语法相关的内容放到了自己的类中。它仅具有一些与语法相关的实用程序,并且允许我们编写独立于语法的语法分析器,以后再担心语法:
    class Grammar {
public:
// I'm in visual studio 2010, which doesn't seem to like the using type = value; syntax, so this instead
typedef std::function<Expression*(Tokenizer::Token,Parser&)> PrefixCallback;
typedef std::function<Expression*(Tokenizer::Token, Expression*, Parser&)> InfixCallback;
private:
std::map<std::string, PrefixCallback> prefix;
std::map<std::string, InfixCallback> infix;
std::map<std::string, double> infixPrecedence; // we'll use double precedence for more flexabillaty
public:
Grammar() {
prefixBindingPower = std::numeric_limits<double>::max();
}

void RegisterPrefix(std::string key, PrefixCallback c) {
prefix[key] = c;
}

PrefixCallback GetPrefixCallback(std::string key) {
return prefix[key];
}

bool HasPrefix(std::string key) {
return prefix.find(key) != prefix.end();
}

void RegisterInfix(std::string key, InfixCallback c, double p) {
infix[key] = c;
infixPrecedence[key] = p;
}

InfixCallback GetInfixCallback(std::string key) {
return infix[key];
}

double GetInfixPrecedence(std::string key) {
return infixPrecedence[key];
}

bool HasInfix(std::string key) {
return infix.find(key) != infix.end();
}
};

最后,我可能需要显示一个解析回调来完成设置:
    Expression* ParenPrefixParselet(Tokenizer::Token token, Parser& parser) {
Expression* value = parser.parseExpression(0);
Expression* parenthesis = new Paren(value); // control of value gets given to our new expression. No need to delete
parser.consume(")");

return parenthesis;
}

这使我能够编写一种语法,该语法允许在括号中进行如下操作:
Grammar g;
g.RegisterPrefix("(", &ParenPrefixParselet);

最后,一个main():
int main() {
Grammar g;
g.RegisterPrefix("(", &ParenPrefixParselet);
Parser parser(g);

Expression* e = parser.parseExpression(0);

std::cout << static_cast<std::string>(*e);

return 0;
}

信不信由你,我认为这是很小的。记住,这是一个解析器。请记住,作为一个最小的示例,我计划对其进行扩展,但是希望您能理解。

最佳答案

您希望使用多态-有两种方法。使用引用或指针。带有引用的东西是当您返回它们时很危险。在大多数情况下,UB是您返回对本地对象的引用。这意味着我们剩下了指针。

但不要使用newdelete。它们是不安全的,难以处理,尤其是在多范围环境中。使用智能指针。使用unique_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
}
}

编辑:

另外还要回答标题为 no 的问题。

关于c++ - 是否从函数未定义的行为返回取消引用的指针作为引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56639607/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com