gsl :: not_null <T*>与std :: reference_wrapper <T>对比T&

Mik*_*ail 49 c++ pointers cpp-core-guidelines guideline-support-library

最近提出了C++核心指南(恭喜!),我担心gsl::not_null类型.如I.12中所述:声明一个不能为null的指针not_null:

帮助避免解除引用nullptr错误.通过避免对nullptr进行冗余检查来提高性能.

...

通过声明源代码中的意图,实现者和工具可以提供更好的诊断,例如通过静态分析查找某些类错误,并执行优化,例如删除分支和空测试.

目的很明确.但是,我们已经有了语言功能.不能为null的指针称为引用.虽然引用一旦创建就无法反弹,但这个问题可以解决std::reference_wrapper.

gsl::not_null和之间的主要区别在于,std::reference_wrapper后者只能用于代替指针,而前者适用于任何事物 - 可nullptr分配(引自 F.17:使用not_null来表示"null"不是有效值):

not_null不只是内置指针.它的工作原理为 array_view,string_view,unique_ptr,shared_ptr,和其他类似指针的类型.

我想象功能比较表如下:

T&:

  • 不能存储nullptr?- 是的
  • Rebindable?-
  • 可以用来代替指针以外的东西吗?-

std::reference_wrapper<T>:

  • 不能存储nullptr?- 是的
  • Rebindable?- 是的
  • 可以用来代替指针以外的东西吗?-

gsl::not_null<T*>:

  • 不能存储nullptr?- 是的
  • Rebindable?- 是的
  • 可以用来代替指针以外的东西吗?- 是的

现在这里是问题,最后:

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

PS我创建了标签cpp-core-guidelines,guideline-support-library为此,我希望正确.

Jos*_*son 31

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

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

正如您所指出的,引用不能反弹(由于它们的值赋值语义),但reference_wrapper<T>类模板可以反弹,因为它具有引用赋值语义.这是因为它reference_wrapper<T>被设计为与STL容器和算法一起使用,并且如果其复制赋值运算符与其复制构造函数不同,则不会正常运行.但是,reference_wrapper<T>仍然具有值比较语义,如引用,因此当与STL容器和算法一起使用时,它与指针的行为非常不同.例如,set<T*>可以包含指向具有相同值的不同对象的指针,同时set<reference_wrapper<T>>可以包含对具有给定值的一个对象的引用.

所述not_null<T*>类模板具有参考分配比较的语义,比如一个指针; 它是一种类似指针的类型.这意味着当与STL容器和算法一起使用时,它的行为类似于指针.它不能为空.

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

  • @Mikhail 我试图重写以澄清。`set` 在任何时候都只包含一个具有任何给定值的对象。`T*` 和 `reference_wrapper&lt;T&gt;` 具有不同的比较语义,因此它们在放入 `set` 时表现不同。 (2认同)
  • 我要指出的是,`reference_wrapper` 本身没有* 比较语义(运算符)。容器看到的这种语义必须通过其隐式转换为引用类型来传递。快速测试表明,我确实可以创建一个 `std::set&lt; std::reference_wrapper&lt;int&gt; &gt;` 并将“引用”放入其中,并获得预期的排序结果和防止重复,但规范和实现包装器不显示比较运算符,因此必须通过隐式转换(在这种情况下为假想的 `bool operator&lt;(int, int)`)获得这些运算符。 (2认同)

Jen*_*ens 10

我认为仍有一些用例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) );
Run Code Online (Sandbox Code Playgroud)

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

这同样适用于算法的仿函数,我也必须使用它进行绑定boost::signals.

  • @Mikhail我不这么认为.线程构造函数将参数复制/移动到线程可访问的存储,然后使用thiese"copies"在线程上下文中调用该函数.如果你想通过引用传递一些东西,你必须使用`std :: ref`. (2认同)
  • 当然,您必须注意引用的对象仍然存在。 (2认同)