自动检测 shared_ptr 应该被替换为 unique_ptr 的时机

3
我正在处理一个庞大的C++代码库,该库在过去10年中逐渐演化和发展。我正在研究的其中一个问题是使用了太多 shared_ptr(通过 make_shared),而这些地方实际上可以使用 unique_ptr 替代。
我们已经取得了很好的进展,并且在重构代码时正确使用指针时性能表现非常出色。但是,我想知道是否有一种方法可以自动检测指针的错误使用情况,并将其替换为 unique_ptr。
这既可以用于“扫描”代码库(类似于clang-tidy),以便更轻松地进行重构,也可以用于持续运行模式,以检测指针误用并在“预提交”级别进行防止。
我查阅了各种文章和stack overflow帖子,同时查看了gcc和clang编译器选项,但还没有找到我需要的解决方案。

1
正确地决定这需要考虑所有代码,而不仅仅是一个翻译单元,我不确定是否有一种好的方法来设置clang-tidy。您可以使用自定义clang-tidy检查器为每个TU提供一些输出,然后有某些东西将这些结果组合起来。 - aschepler
谢谢您的回复,我的问题不是“如何使用tidy来做这件事”,而是是否有一些“扫描工具”(无论是编译器标志还是其他任何工具),可以帮助我们轻松些。该线程中的其他评论非常有用,可以检查代码并确保代码建模正确。 - Jon Kohler
2个回答

7
我不知道有自动化的方法来完成这个任务,但你可以:
  1. 暂时用unique_ptr替换所有shared_ptr
  2. 每次编译器报错时:
    • 判断是否可以使用移动语义
    • 如果可以,unique_ptr就足够了
    • 否则,回到shared_ptr
  3. 其他情况下,使用unique_ptr即可
然而,这种方法比较粗糙;最终还是需要用大脑仔细分析每个用法。我知道对于一个大型代码库来说,这可能会很麻烦。总之,解决这个问题(如果你能回到过去的话)的最好方法是不要在十年内引入越来越多的技术债务!

4
除此之外,让更多的初级开发人员参与指针语义的教育,让他们完成这项工作。他们会非常讨厌这份工作,并发誓在未来更加慎重地使用指针。 - paddy
嘿,我喜欢那个。你仍然需要审查他们的每一个建议,所以对于OP本人来说并不是真正的“减轻”工作量...但我想这对于任何分配给下属的大任务都是如此,虽然这样做对于执行猴子工作的人以及长期来看对组织(和我们的行业)也有好处。_(tl;dr: 你得教人)_ - Lightness Races in Orbit
1
还可以用于检测哪些“public”函数实际上应该被设置为“private”。 - dan04
2
@dan04:嗯。我不会仅仅因为可以将函数设为“private”而这样做;函数的访问级别应该与该函数的含义相符,即使您目前不依赖它的自由度。但是有人可能会争辩说,除非您实现了很多尚未使用的东西(一些人建议不要这样做!),否则您的方法通常会起作用。 - Lightness Races in Orbit
感谢回复,这基本上是我们一直在做的(深入建模/设计,并深入研究移动到unique_ptr并查看哪些地方出现问题),然后重复洗涤/漂洗。完全同意没有免费的午餐,除了确保技术债务不会累积。我们正在通过多种方式解决这个问题,这是作为一个疯狂快速发展的初创公司的副产品。这就是我第二个问题的出处,询问是否有一种方法可以在提交时检测到这个问题。显然,好的CRs有助于 :) 但有时可能会错过一些东西,因此提出了这个问题。 - Jon Kohler
@JonKohler 玩得开心 :P - Lightness Races in Orbit

0

我会有另一种方法。创建一个包含一个 std::shared_ptr 的类。尝试看看为什么它是共享的(主要是为了能够复制类或在多个位置保存指针)。看看你可以使用什么解决方案。

我进行重构的方式是从理论上审视上下文。在纸上,这两个类(共享指针的容器和指针)的生命周期是否已知?它们的生命周期是否相关或无关?通过将这两个类的生命周期链接在一起或链接到系统来解决问题。尝试使您的模型清晰。记录下来。自然地,通过将生命周期组合成逻辑单元,您可以引入更多相关的类。

然后,在完成模型后,将其转换为代码。尽可能分阶段进行,并仔细计划。只有在必须了解您操作的对象的生命周期时才使用 std::unique_ptrstd::shared_ptr。否则,请考虑(非拥有)原始指针或引用。代码应尽可能接近您的模型。如果在实现时出现问题,请修改您的模型。


如果您不了解正在操作的对象的生命周期,那是一个严重的问题,必须加以解决。使用原始指针只会掩盖这个问题。 - Lightness Races in Orbit
@LightnessRacesinOrbit,有很多函数不需要知道其参数的所有权和生命周期。例如,当我只需要观察对象时,我不知道为什么要在各处传递std::unique_ptr - Guillaume Racicot
如果你想观察一个对象,那么你需要知道它是否还活着。我并没有说“永远不要使用裸指针”,只是“不要使用裸指针来掩盖当你_不知道对象的生命周期_时”,因为你应该始终知道对象的生命周期,而你选择的指针类型并不影响这一点。顺便说一句,C++20将引入observer_ptr,它基本上只是一个裸指针,但清楚地表示了你刚才描述的意图;我还不确定自己对此的感觉如何,但就是这样。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接