如何在C++中启用Rust Ownership范例

Chr*_*bek 25 c++ boost smart-pointers rust

系统编程语言Rust使用所有权范例来确保在编译时,在必须释放资源时运行时的成本为零(请参阅"Rust Book on Ownership").

在C++中,我们通常使用智能指针来实现隐藏管理资源分配的复杂性的相同目标.但是有一些不同之处:

  • 在Rust中,始终只有一个所有者,而C++ shared_ptr很容易泄漏所有权.
  • 在Rust中我们可以借用我们不拥有的引用,而C++ unique_ptr不能通过weak_ptr和lock()以安全的方式共享.
  • shared_ptr的引用计数代价很高.

我的问题是:我们如何在以下约束条件下模拟C++中的所有权范例:

  • 任何时候只有一个所有者
  • 可以借用指针并暂时使用它而不用担心资源超出范围(observer_ptr对此无用)
  • 尽可能多的编译时检查.

编辑: 鉴于目前为止的评论,我们可以得出结论:

  • 在编译器中没有编译时支持(我希望有一些我不知道的decltype /模板魔法).可能在其他地方使用静态分析(污点?)
  • 没有引用计数就无法得到这个.
  • 没有标准实现来区分shared_ptrs与拥有或借用语义
  • 可以通过在shared_ptr和weak_ptr周围创建包装类型来自己滚动:

    • owned_ptr:不可复制,移动语义,封装shared_ptr,访问borrowed_ptr
    • borrowed_ptr:copyable,封装weak_ptr,lock方法
    • locked_ptr:不可复制的移动语义,从锁定weak_ptr封装shared_ptr

小智 26

你根本无法使用编译时检查.当对象超出范围,被移动或被破坏时,C++类型系统缺乏任何推理方法 - 更不用说将其变成类型约束.

你可以做的是有一个变体,unique_ptr它可以计算出在运行时有多少"借用".而不是get()返回原始指针,它会返回一个建设递增此计数器,并减小它破坏的智能指针.如果在unique_ptr计数非零时销毁,至少你知道有人做错了什么.

但是,这不是一个万无一失的解决方案.无论你多么努力地阻止它,总会有办法获得一个指向底层对象的原始指针,然后它的游戏结束,因为原始指针可以很容易地比智能指针更长unique_ptr.甚至有时需要获取原始指针,以与需要原始指针的API进行交互.

此外,所有权是不是指针.Box/ unique_ptr允许你堆分配一个对象,但与将相同的对象放在堆栈上(或在另一个对象内,或其他任何地方)相比,它不会改变所有权,生命周期等.为了在C++中从这样的系统中获得相同的里程数,你必须为所有对象制作这样的"借用计数"包装器,而不仅仅是unique_ptrs.这是非常不切实际的.

那么让我们重新审视编译时选项.C++编译器无法帮助我们,但也许lints可以吗?理论上,如果您实现类型系统的整个生命周期部分并为您使用的所有API添加注释(除了您自己的代码),这可能有效.

但它需要为整个程序中使用的所有函数添加注释.包括第三方库的私人帮助功能.那些没有源代码的人.对于那些执行过于复杂而无法理解的人来说(从Rust经验来看,有时某些东西是安全的,在生命周期的静态模型中表达得过于微妙,而且必须以不同的方式编写,以帮助编译器).对于最后两个,linter无法验证注释是否确实正确,因此您又回到了信任程序员的位置.此外,一些API(或者更确切地说,它们是安全的条件)在Rust使用它时在生命周期系统中无法表达得非常好.

换句话说,对于这一点来说,一个完整且实用的物证将是大量的原始研究以及相关的失败风险.

也许有一个中间地带可以获得80%的收益和20%的成本,但是因为你需要一个很难保证(老实说,我也喜欢这样),运气不好.C++中现有的"良好实践"已经通过基本思考(和记录)Rust程序员的方式,在没有编译器辅助的情况下,最大程度地降低了风险.考虑到C++的状态及其生态系统,我不确定是否有很多改进.

tl; dr只需使用Rust ;-)

  • 谢谢!几点:(1)借用计数很容易——我们可以用owner_ptr或borrowed_ptr替换所有出现的shared_ptr,(2)Murphy是敌人,而不是Machiavelli,因此它只需要很难做错事。(3) unique_ptr 不是一个好的起点(恕我直言),因为它没有处理观察者的设施(见编辑)。(4) 我们正在使用约定来防止循环 shared_ptr 依赖,但是太容易在脚 (TM) 中射击自己。(5) 使用 Rust 是不可能的 ATM,因为它比 C++ 强大得多(虽然我们欣赏新想法!)。 (3认同)