本周小贴士#126: ‘make_unique‘是新的‘new‘

作为totW#126最初发表于2016年12月12日

由James Dennett (jdennett@google.com)基于Titus Winters (titus@google.com)的邮件列表创作

随着代码库的扩展,越来越难以了解你依赖的每件事的细节。需要深入的知识无法扩展:我们必须依靠接口和契约来知道代码是正确的,无论是在写还是审查时。在许多情况下,类型系统可以用一种通用的方式来提供这些契约。类型系统契约使用的一致性,通过识别在堆上分配的对象存在潜在风险分配或所有权转移的位置,可以更轻松地编写和审查代码。

虽然在C++中,我们可以通过使用纯值来减少动态内存分配的需求,但是有时我们需要对象的生命周期超过其作用域。在动态分配对象时,C++代码应该优先使用智能指针(最常见的std::unique_ptr)而不是原生指针。这提供了关于分配和所有权转移一致性,并在那些需要更仔细审查代码所有权问题的地方留下了更清晰地视觉提示。满足C++14之后,在外部如何分配及异常安全上的副作用只是小事。

关于此的两个关键工具是absl::make_unique()(c++14的std::make_unique的C++11的实现,用于免泄露动态分配)和absl::WrapUnique()(用于包装拥有指向相应std::unique_ptr类型的原生指针)。它们可以在absl/memory/memory.h中找到。

为什么要避免new?

为什么代码要优先使用智能指针和分配函数而不是原生指针和new呢?

  1. 如果可能,所有权最好表达在类型系统中。这允许审查者几乎完全通过本地检查来验证正确性(没有泄露和重复删除)。(在对性能异常敏感的代码中,这可能是可能原谅的:虽然代价小,但是由于ABI的约束,以值的方式跨函数传递std::unique_ptr的开销是非零。这不够重要到证明需要避免它。)
  2. 有点像优先push_back()而不是emplace_back()的原因(TotW 112),absl::make_unique()直接表达了目的并且只能做一件事(使用公共构造函数分配,返回指定类型的std::unique_ptr)。这里没有类型转换或隐式行为。absl::make_unique在做明面上所讲的。
  3. 同样可以用 std::unique_ptr my_t(new T(args))来完成;但这是多余的(重复类型名称 T),对于某些人来说,尽量减少对 new 的调用是有价值的。关于这的更多信息在#5。
  4. 如果所有分配都通过 absl::make_unique() 或工厂调用处理,则将 absl::WrapUnique() 用于实现这些工厂调用,用于与不依赖 std::unique_ptr 所有权的传统方法交互的代码转移,以及在极少数情况下需要通过聚合初始化动态分配 (absl::WrapUnique(new MyStruct{3.141, “pi”}))。在代码审查中,很容易发现 absl::WrapUnique 调用并评估“该表达式看起来像所有权转移吗?”通常很明显(例如,它是一些工厂函数)。当它不明显时,我们需要检查函数以确保它实际上是原始指针所有权转移。
  5. 如果我们主要依赖std::unique_ptr的构造函数,那么我们会看到如下的调用:
    std::unique_ptr foo(Blah());
    std::unique_ptr bar(new T());
    只需要片刻的检查就可以看到后者是安全的(没有泄露,没有重复删除)。前者呢?它取决于:如果Blah()返回一个std::unique_ptr,那很好,尽管如此,如果写成下面那样会更安全
    std::unique_ptr foo = Blah();
    如果Blah()是返回一个传递了所有权的原生指针,那也是可以的。如果Blah()只返回一些随机指针(没有传递),那么就有问题了。依赖absl::make_unique() 和 absl::WrapUnique()(避免构造函数)为我们担心的地方提供了额外的视觉提示()(调用 absl::WrapUnique(),并且仅仅如此)。

我们应该如何选择使用哪一个呢?

  1. 默认情况下,使用absl::make_unique()(或者用std::make_shared()来处理那些共享所有权的少有的情况是适合的)来动态分配。例如,使用auto bar = absl:make_unique()替代std::unique_ptr bar(new T());并且用 bar = absl::make_unique();来代替bar.reset(new T());
  2. 在使用非公有函数的工厂函数中,返回std::unique_ptr,在实际中使用absl::WrapUnique(new T(…)).
  3. 当动态分配一个需要用大括号初始化(典型的结构体,数据和容器)的对象时,使用absl::WrapUnique(new T{…})。
  4. 当调用一个通过T*接受所有权的传统API时,要么提前使用absl::make_unique分配对象然后调用ptr.release(),要么直接在函数参数中使用new.
  5. 当调用一个通过T返回所有权的传统API时,请立即用WrapUnique构造一个智能指针(除非你打算立即传递一个指针到另外一个接受T形式所有权的传统API)。

总结

相较于absl::WrapUnique()请优先使用absl::make_unique(),相较于原生new优先使用absl::WrapUnique().

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值