在作用域中保持shared_ptr

Keeping a shared_ptr in scope

本文关键字:shared ptr 作用域      更新时间:2023-10-16

我有一个类,它在一堆共享指针上工作,并将它们存储在各种(而不是互斥)集合中。类的任务之一是在添加/删除这些共享指针时保持这些容器是最新的。

为了方便维护这些集合,我有一些辅助函数来对共享指针进行分类,将其从所有相关的容器中添加/删除,并执行任何其他必要的工作。除了,如果在容器的元素上直接调用remove函数,则在函数完成之前释放共享指针。

我已经通过按值传递元素给函数解决了这个问题。这为函数提供了自己的共享指针副本,并使其保持活动状态,直到函数结束。我对这个解决方案很满意(并且需要/动机被注释了),但是这个函数不断被代码审计工具(例如clang-tidy)标记/更改为性能差并更改为const引用。

如何避免这种情况?这个函数只是一个更大的库的一小部分,维护人员缺少注释是可以理解的。我不能改变代码审计规则,所以我想一个简单而有效的方法来避免这个问题?我怀疑c++ 11和std::move可能有一些聪明的地方?

例如,如果我的类使用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引用一起工作,或者让代码审计工具/读者清楚地知道它不能?

编辑注意:

  • FruitPtrstd::shared_pointer<Fruit>
  • 指针的唯一副本可能在removeFruit操作的容器中(假设它们是)。

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

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>的类型定义/使用。

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

简单的解决方案是这样写:

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

这个问题存在于两个层面:一个是编写实现所需所有权/生命周期语义的代码的技术层面,另一个是您和库维护人员之间的沟通和管理问题——它似乎使解决第一个问题变得相当困难。

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

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