可空引用的 C++ 惯用方式

C++ idiomatic way for nullable reference

我正在编写一个函数,它应该接受 const std::string &,但也接受一个特殊的 nullptr 值。目前,我正在使用 const std::string *。但是,我觉得应该有一种更像 C++ 的方式来执行此任务。

我最强烈的反对意见是按值传递(字符串不会很长,但 64kB 不是我想要复制的东西)。这也意味着我不想要像 optional<T> 这样具有类型 T 的值字段的对象。另外,我不想使用任何外部库。我的项目没有使用 Boost 或 GS​​L,只是普通的 C++(准确地说是 C++17)。

是否有标准库class(奇迹发生在namespace std)或这种情况下被广泛接受的成语?

写两个函数:

void f(const string *s)
{
    if (s)
    {
        // Use s
    }
    else
    {
        // Don't use s
    }
}

void f(const string &s)
{
    f(&s);
}

如果 std::optional 支持引用,这将是一种奇特的 "modern" 方式。

但是:

  • 没有,而且
  • 有时很花哨,"modern" 并不像宣传的那样。

age-old、tried-and-tested和clear-to-read的解决办法是取指针。这种方法绝对可以完成您需要它做的所有事情。

就这么办,然后继续把宝贵的时间花在更重要的事情上!

另一种方法是提供一个空字符串,您的函数可以通过对象身份检查来检测它:

#include <string>

namespace MyLibrary
{
   static const std::string NULL_STRING;

   void foo(const std::string& str)
   {
      if (&str == &NULL_STRING)
         // ...
      else
         // ...
   }
}

int main()
{
   MyLibrary::foo("Hello, world!");
   MyLibrary::foo("");

   MyLibrary::foo(MyLibrary::NULL_STRING);
}

不过,有人可能会认为这是对 space 的一种浪费,而且我不认为它特别地道。但是,有时我在私有库代码中采用了类似的方法(特别是与默认参数相结合)。

这个问题的一个有趣的方面是我更常见的是反向透视:指定不允许为空的指针的C++方式是什么?答案归结为相同的原则。

指针与引用之间的主要区别是:

  1. 可以使指针指向不同的对象。
  2. 指针可以为空。

如果您使指针保持不变,第一个差异就会消失。拥有 "nullable reference" 的 C++ 方法是拥有一个常量指针。请注意,我说的是指针是常量,如 T * const,它独立于 pointed-to 对象是常量,如 T const *const T *.

您可能受过训练认为所有指针都是邪恶的,但那是 over-generalization。原始指针是引用可​​选对象的好方法负责其内存。如果您负责释放内存,请使用智能指针,但如果其他人为您持有内存,请使用引用或指针。如果需要对象(不能为空),则使用引用;一个指针,如果它是可选的(可以为 null)。哦,这假定您不需要更改哪个对象是 pointed-to,在这种情况下,引用将不再是一个选项。

话虽这么说,但在特定情况下可能会有其他选择。如果 null 和 not-null 情况之间没有共享代码,那么重载函数可能会更好,例如 void foo(const std::string &)void foo()。这必须与各种 trade-offs 相平衡,包括 API 一致性。选择最适合您情况的方法。

正如你特别要求的一些 STD 库奇迹:STD 库实际上有一个 reference_wrapper 可以与 optional 结合以在某种程度上实现你的要求:

template <class T>
using optional_ref = std::optional<std::reference_wrapper<T>>;

它实际上非常简单,但访问起来有点麻烦:

void foo(optional_ref<std::string> str)
{
    if (str)
        printf("%s", str->get().c_str());
}

从调用站点来看,它工作得很好:

std::string str;
foo(str); // passes a reference
foo({}); // passes nothing

话虽如此,我也不认为使用 ;) 是个好主意(请参阅已接受的答案)。