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

C++ 是否将取消引用的指针作为函数未定义行为的引用返回?,c++,language-lawyer,C++,Language Lawyer,这是我第一次编写解析器。我跟在后面。我要让它工作,但我遇到了一个问题 最初的教程是用Java编写的。我更喜欢C++,这就是我写的。我基本上能够将大部分代码移植到C++(虽然我确实把它变成了“我的”),因为它有一些非语言相关的差异。我唯一真正的问题是这一行代码: 这在java中很好(我假设。我以前从来没有真正用java工作过,但是我假设这个家伙知道他在做什么),但是C++里没有这么多。通过使用如下指针,我能够完成同样的事情: Expression* parse(Parser& parser

这是我第一次编写解析器。我跟在后面。我要让它工作,但我遇到了一个问题

最初的教程是用Java编写的。我更喜欢C++,这就是我写的。我基本上能够将大部分代码移植到C++(虽然我确实把它变成了“我的”),因为它有一些非语言相关的差异。我唯一真正的问题是这一行代码:

这在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);
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
    }
}