首页 > 解决方案 > 如何防止使用 std::shared_ptr 的代码中的内存泄漏

问题描述

最近我一直在研究 C++ 中的方案解释器。对于版本 2,我对其进行了重写,因此它在内部使用了一个 cons 单元的链接列表。到目前为止,一切都很好。现在我正在实现追加,它应该采用两个列表并将它们连接成两个一个列表。下面显示的代码有效。它输出:

(1,2)
(3,4)
(1,2,3,4)

正如它应该。然而 valgrind 显示它正在泄漏内存。(肯定丢失:2 个块中的 80 个字节间接丢失:8 个块中的 240 个字节)我认为这是因为我在 main() 中创建的所有对象它们的指针都被复制到 Cons 的构造函数中,并且副本永远不会被删除。这是正确的吗?

所以我应该通过引用传递它们或以某种方式移动它们对吗?不幸的是,我这样做的尝试导致了更多错误。谁能告诉我如何使这段代码无泄漏?

typedef enum { CONS = 0, ATOM = 1, FUNCTION = 2, LAMBDA = 3 } Type;

class Expression {
public:
    explicit Expression(Type type) : type_{type} {
    }

    virtual ~Expression() {}

    Type type_;
};

class Cons : public Expression {
public:
    Cons(Expression* car = nullptr, Expression* cdr = nullptr) :
    Expression(Type::CONS), car_{car}, cdr_{cdr} {
    }

    ~Cons() {
        if (car_) {
            delete car_;
        }
    }

    Expression* car_;
    std::shared_ptr<Expression> cdr_;
};

class Atom : public Expression {
public:
    Atom(const char* value) : Expression(Type::ATOM), value_{value} {
    }

    Atom(std::string value) : Atom(value.c_str()) {
    }

    std::string value_;
};

std::ostream& operator<<(std::ostream& out, Expression* exp) {
    switch(exp->type_) {
        case Type::ATOM:
            out << dynamic_cast<Atom*>(exp)->value_;
            break;
        case Type::CONS: {
            out << "(";
            auto current = dynamic_cast<Cons*>(exp);
            while (current) {
                out << current->car_;
                if (current->cdr_) {
                    out << ' ';
                }
                current = dynamic_cast<Cons*>(current->cdr_.get());
            }
            out << ")";
            break;
        }
        case Type::FUNCTION:
        case Type::LAMBDA:
            break;
    }

    return out;
}

void traverse(Expression* exp, std::function<void(Expression*)> process) {
    if (exp) {
        if (exp->type_ == Type::CONS) {
            auto cell = dynamic_cast<Cons*>(exp);
            traverse(cell->car_, process);
            traverse(cell->cdr_.get(), process);
        } else {
            process(exp);
        }
    }
}

Expression* append(Expression* first, Expression* second) {
    Cons* ret = nullptr;
    auto add_to_ret = [&ret](Expression* cell) -> void {
        if (ret == nullptr) {
            ret = dynamic_cast<Cons*>(new Cons(cell));
        } else {
            auto temp = ret;
            while(temp->cdr_) {
                temp = dynamic_cast<Cons*>(temp->cdr_.get());
            }
            temp->cdr_.reset(new Cons(cell));
        }
    };

    traverse(first, add_to_ret);
    traverse(second, add_to_ret);

    return ret;
}

int main() {
    Expression* list1 = new Cons(
            new Atom("1"),
            new Cons(
                new Atom("2"),
                nullptr
            )
        );
    std::cerr << list1 << '\n';

    Expression* list2 = new Cons(
            new Atom("3"),
            new Cons(
                new Atom("4"),
                nullptr
            )
        );
    std::cerr << list2 << '\n';

    Expression* joined = new Cons(
        list1,
        nullptr
    );

    joined = append(joined, list2);

    std::cout << joined << '\n';

    if (joined) {
        delete joined;
    }

    return EXIT_SUCCESS;
}

标签: c++c++17

解决方案


如何防止使用 std::shared_ptr 的代码中的内存泄漏

避免生吃newdelete. 始终使用std::unique_ptrorstd::shared_ptr当您需要拥有指针时。仅将原始指针用作非拥有句柄。使用std::make_sharedstd::make_unique创建智能指针拥有的对象。不要打电话.release()unique_ptrs。差不多就是这样。


推荐阅读