69

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

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

...

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

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

gsl::not_null和我之间的主要区别在于std::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为此,我希望是正确的。

4

2 回答 2

56

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

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

正如您所指出的,引用不能被重新绑定(由于它们的值赋值语义),但reference_wrapper<T>类模板可以被重新绑定,因为它具有引用赋值语义。这是因为reference_wrapper<T>被设计用于 STL 容器和算法,如果它的复制赋值运算符没有做与它的复制构造函数相同的事情,那么它的行为就不会正确。但是,reference_wrapper<T>它仍然具有值比较语义,如引用,因此在与 STL 容器和算法一起使用时,它的行为与指针非常不同。例如,set<T*>可以包含指向具有相同值的不同对象的指针,而set<reference_wrapper<T>>可以包含对具有给定值的仅一个对象的引用。

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

因此,您的评估是正确的,只是您忘记了比较语义。不,reference_wrapper<T>不会被任何类型的指针式类型淘汰,因为它具有类似引用的值比较语义。

于 2017-01-30T13:44:59.987 回答
11

我认为仍有一些用例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

于 2015-10-24T07:32:47.090 回答