在范围内保持 shared_ptr

Keeping a shared_ptr in scope

我有一个 class 可以处理一堆共享指针并将它们存储在各种(不是 互斥的)集合中。 class 的任务之一是在 adds/removes 这些共享指针之一时使这些容器保持最新。

为了便于维护这些集合,我有一些辅助函数可以 class 化一个共享指针,add/remove 它来自所有关联的容器并执行任何其他必要的工作。这很有效 除了 如果直接对容器的元素调用 remove 函数,共享指针在函数完成之前被释放。

我通过按值将元素传递给函数解决了这个问题。这为函数提供了它自己的共享指针副本,并使其保持活动状态直到函数结束。我对这个解决方案很满意(need/motivation 被评论了)但是这个函数一直被代码审计工具(例如,clang-tidy)flagged/changed 认为性能不佳并更改为 const 引用。

如何避免这种情况?该函数只是更大库的一小部分,维护者遗漏注释是可以理解的。我无法更改代码审核规则,所以我想要一种简单有效的方法来避免这个问题?我怀疑 C++11 和 std::move?

可能有一些聪明的东西

例如,如果我的 class 正在使用 FruitPtr 共享指针,它可能有诸如

之类的集合
std::vector<FruitPtr> greenFruit_;
std::vector<FruitPtr> redFruit_;
std::vector<FruitPtr> sweetFruit_;
std::vector<FruitPtr> sourFruit_;

等等

有问题的函数看起来像

removeFruit(FruitPtr oldFruit)
{
    // Remove the element from any containers it belongs to:
    if (/*Some container condition*/)
    {
        //Find and remove from container
    }
    // etc., for all containers

    // Do some final operations on the element that must occur after it is removed from the containers,
    oldFruit->markSpoiled();
}

这工作正常,但是如果它被更改为removeFruit(const FruitPtr& oldFruit)然后当直接调用容器的元素时,例如removeFruit(greenFruit_[i]),指针oldFruit 一旦从所有容器中移除,就会在对元素本身执行最终操作之前销毁。在我的库中,这些操作必须在函数末尾执行,因为它们会影响在容器中查找元素。

那么,我该如何让这个函数与 const 引用一起工作,或者让代码审计 tools/readers 清楚地知道它不能?

编辑 注:

我的直接解决方案是让函数自己制作副本,例如

removeFruit(const FruitPtr& oldFruit)
{
    FruitPtr fruitToRemove(oldFruit)

    //...

    // Use fruitToRemove everywhere in the function, e.g.,
    fruitToRemove->markSpoiled();
}

但是还有更聪明的东西吗?有点像 C++11 中的 std::move?

智能指针不是无需担心生命​​周期或指针的解决方案,但是,它们可以使简单的情况变得不那么复杂。

让我假设 FruitPtrstd::shared_ptr<Fruit> 的 typedef/using。

在这种情况下,我们可以假设此指针的唯一副本在您的存储中,否则您不会发生崩溃。

简单的解决方案是编写以下内容:

void removeFruit(FruitPtr oldFruit);

通过这样做,您将在堆栈上获得 shared_ptr 的副本,之后将清理实际的实现。

假设您不想要 shared_ptr 的副本,可以编写以下内容:

void removeFruit(const FruitPtr &oldFruit) {
     FruitPtr tempStorage;
     switch (oldFruit.getType()){
         case Fruit::Type::Green: {
             auto itFind = std::find(begin(greenFruit_), end(greenFruit_), oldFruit);
             assert(itFind != end(greenFruit_));
             tempStorage = std::move(*itFind);
             greenFruit_.erase(itFind);
             break;
          }
          //...
     }
     // Some code
     tempStorage->markSpoiled();
} // Destroys instance if tempStorage.unique() == true.

最后,有人可能想知道为什么还要使用 markSpoiled 函数。 这很可能是因为其他代码共享所有权,而实际上它不应该拥有它。在这种情况下,std::weak_ptr 就是您所需要的。

任何代码仍然对 Fruit 有一些引用,将存储 std::weak_ptr<Fruit> 并且必须调用 lock() 以获得 shared_ptr。如果此 shared_ptr 的最后一个实例被删除,则此指针将是 nullptr

这个问题存在于两个层面:一个是编写实现所需 ownership/lifetime 语义的代码的技术层面,另一个是您与库维护者之间的沟通和管理问题 - 它出现使解决第一个问题变得更加困难。

关于解决第二个问题,除了与维护者交谈并耐心而圆滑地解释他们对代码的更改会改变其语义之外,我没有太多建议可以建议,并且需要您仔细考虑并达成一致。虽然他们可能只是盲目地应用审计工具生成的建议,但他们很可能有令人信服的理由坚持这些更改。当然这个我说起来容易,你做起来难。

解决该技术问题的一种可能方法是 removeFruit() return 输入 FruitPtr 的副本 - 这将使 Fruit 对象保持活动状态,直到调用者决定如何处理它。