在这种情况下我应该使用智能指针吗?

Should I use smart pointers in this situation?

我知道首选规则是始终使用 shared_ptr 或其他智能指针。但是,我不确定如何在不使用 std::make_shared 进行复制的情况下准确地实现它。我会详细说明:

问题

我想创建一个树状表达式,例如:

struct foo{
    std::vector<foo*> v;
};

这允许我执行以下操作:

foo add(foo& f1, foo& f2){
    return foo {&f1, &f2};
}

那很好,因为那样的话两者就会成为新节点的子节点。

我希望正确评估以下内容:

foo x, y;
auto z = add(x,y);
assert(z.v[0] == &x && z.v[1] == &y);

这样,没有抄袭,一切都很好。但是,我们这里有一个资源管理问题。

解决资源管理问题

如果这些元素是在堆上分配的,而有人可能会在不知不觉中分配,那么我们就会 运行 发生内存泄漏。所以最好的方法是对 RAII 使用智能指针:

struct foo{
    std::vector<std::shared_ptr<foo> > v;
};

foo add(foo& f1, foo& f2){
    return foo {std::make_shared<foo>(f1), std::make_shared<foo>(f2)};
}

这很好,但是我们在这里调用 f1 和 f2 的复制构造函数。假设复制需要很长时间,我们不想承担那个成本。

我们绝对不能做:

foo add(foo& f1, foo& f2){
    return foo {std::shared_ptr<foo>(&f1), std::shared_ptr<foo>(&f2)};
}

因为每当我们删除 z = x+y 中的 z 时,我们都会在 xy 中调用 delete,它们可以分配在堆。 而且我们绝对不想delete栈上的东西。

那么遇到这种情况我该怎么办呢?

如果我应该提供更多关于上下文的信息,请告诉我。

通过智能指针接受。正确应用智能指针的概念意味着您必须考虑数据的所有权语义,而不是自动调用 new/delete。

您接受了对非常量对象的引用。这并不意味着共享,仅意味着对象的修改。正如您正确指出的那样,仅创建指向它们的智能指针就会带来灾难。

add 的调用者(即使只是您)必须知道他们传递的数据将被共享。他们知道的唯一方法是他们自己是否传递了智能指针。所以你应该add改成这样:

foo add(std::shared_ptr<foo> f1, std::shared_ptr<foo> f2){
    return foo{f1, f2};
}

现在,add 的用户唯一能炸毁它的方法就是故意。但他们很可能不会。此外,现在所说的用户在调用您的代码时具有更大的灵活性。他们可以控制他们传递的每个对象的分配器和删除器。