gsl::not_null<T*> vs. std::reference_wrapper<T> vs. T&

gsl::not_null<T*> vs. std::reference_wrapper<T> vs. T&

本文关键字:gt vs lt std wrapper reference gsl not null      更新时间:2023-10-16

C++ 核心指南最近已经提出(恭喜!(,我担心gsl::not_null类型。如 I.12 中所述:将不得为 null 的指针声明为 not_null

帮助避免取消引用 nullptr 错误。通过以下方式提高性能 避免对 nullptr 进行冗余检查。

通过陈述意图 源、实施者和工具可以提供更好的诊断,例如 通过静态分析查找某些类别的错误,并执行 优化,例如删除分支和空测试。

意图很明确。但是,我们已经为此提供了语言功能。不能为 null 的指针称为引用。虽然引用一旦创建就无法重新绑定,但这个问题可以通过std::reference_wrapper来解决。

我看到gsl::not_nullstd::reference_wrapper之间的主要区别在于后者只能用于指针而不是指针,而前者适用于nullptr可分配的任何内容(引用自 F.17:使用not_null表示"null"不是有效值(:

not_null不仅适用于内置指针。它适用于 array_viewstring_viewunique_ptrshared_ptr等 类似指针的类型。

我想象功能比较表如下所示:

T&

  • 无法存储nullptr ? -
  • 可重新绑定? -
  • 可以用代替指针以外的东西吗? -

std::reference_wrapper<T>

  • 无法存储nullptr ? -
  • 可重新绑定? -
  • 可以用代替指针以外的东西吗? -

gsl::not_null<T*>

  • 无法存储nullptr ? -
  • 可重新绑定? -
  • 可以代替指针以外的其他东西吗? - 是的
现在,最后是

问题:

  1. 我对这些概念之间差异的理解是否正确?
  2. 这是否意味着std::reference_wrapper现在毫无用处?

PS我为此创建了cpp-core-guidelinesguideline-support-library标签,我希望正确。

引用不是不能为空的指针。引用在语义上与指针非常不同。

引用具有值赋值和比较

语义;即涉及引用读取和写入引用值的赋或比较操作。指针具有(违反直觉的(引用赋值和比较语义;也就是说,涉及指针读取和写入引用本身(即引用对象的地址(的赋值或比较操作。

如前所述,引用不能重新绑定(由于其值赋值语义(,但 reference_wrapper<T> 类模板可以重新绑定,因为它具有引用赋值语义。这是因为reference_wrapper<T>旨在与 STL 容器和算法一起使用,如果其复制赋值运算符不执行与其复制构造函数相同的操作,则其行为将不正确。但是,reference_wrapper<T>仍然具有值比较语义(如引用(,因此当与 STL 容器和算法一起使用时,它的行为与指针非常不同。例如,set<T*>可以包含指向具有相同值的不同对象的指针,而set<reference_wrapper<T>>可以仅包含对具有给定值的一个对象的引用。

not_null<T*>类模板具有引用赋值比较语义,类似于指针;它是一种类似指针的类型。这意味着当与 STL 容器和算法一起使用时,它的行为类似于指针。它只是不能为空。

所以,你的评估是对的,除了你忘记了比较语义。不,reference_wrapper<T>不会被任何类型的指针类型淘汰,因为它具有类似引用的值比较语义。

我认为std::reference_wrapper仍有gsl::not_null未涵盖的用例。基本上,std::reference_wrapper镜像引用并具有operator T&转换,而not_null具有与operator->的指针接口。我立即想到的一个用例是在创建线程时:

void funcWithReference(int& x) { x = 42; }
int i=0;
auto t = std::thread( funcWithReference, std::ref(i) );

如果我无法控制funcWithReference,我就无法使用not_null

这同样适用于算法的函子,我也不得不使用它来绑定boost::signals