C++ C++;可空引用的惯用方法

C++ C++;可空引用的惯用方法,c++,reference,nullable,C++,Reference,Nullable,我正在编写一个函数,它应该接受const std::string&,但也接受一个特殊的nullptr值。目前,我使用的是const std::string*。然而,我觉得应该有一种更像C++的方式来执行这项任务 我最强的对位是通过值传递(字符串不会很长,但64kB不是我想要复制的东西)。这也意味着我不需要像optional这样的对象,它的值字段类型为T。另外,我不想使用任何外部库。我的项目不是使用Boost或GSL,只是简单的C++(C++ 17要精确)。 是否有一个标准的库类(奇迹发生在名称空

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

我最强的对位是通过值传递(字符串不会很长,但64kB不是我想要复制的东西)。这也意味着我不需要像
optional
这样的对象,它的值字段类型为
T
。另外,我不想使用任何外部库。我的项目不是使用Boost或GSL,只是简单的C++(C++ 17要精确)。 是否有一个标准的库类(奇迹发生在
名称空间std
)或一个广泛接受的习惯用法用于这种情况?

编写两个函数:

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

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

如果
std::optional
支持引用,这将是一种奇特的“现代”方式

但是:

  • 没有,而且
  • 有时,花哨和“现代”并不是人们所说的全部
古老的、经过试验和测试的、清晰易读的解决方案是使用指针。这种方法绝对可以完成您需要它完成的所有事情


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

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

#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);
}
#包括
名称空间MyLibrary
{
静态常量std::string NULL\u string;
void foo(const std::string和str)
{
if(&str==&NULL\u字符串)
// ...
其他的
// ...
}
}
int main()
{
我的图书馆::foo(“你好,世界!”);
MyLibrary::foo(“”);
MyLibrary::foo(MyLibrary::NULL\u字符串);
}

也许人们会认为这有点浪费空间,但我不认为它特别地道。然而,我在私人图书馆代码中也采取了类似的方法,有时(特别是与默认参数耦合)。< /P> < P>这个问题的一个有趣的方面是,我更常见地看到了反向的观点:什么是C++方法来指定一个不允许为空的指针?答案归结为相同的原则

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

  • 可以使指针指向不同的对象
  • 指针可以为空
  • 如果将指针设为常量,则第一个差异消失。有一个“空引用”的C++方法是有一个常数指针。请注意,我所说的指针是常量,如
    T*const
    ,它独立于指向的对象是常量,如
    T*const
    const T*

    你可能被训练成认为所有的指针都是邪恶的,但这是一种过度概括。原始指针是引用可选对象的一种好方法,您对这些对象的内存不负责任。如果您负责释放内存,请使用智能指针,但如果其他人为您持有内存,请使用引用或指针。如果需要对象,则使用引用(不能为空);指针,如果它是可选的(可以为空)。哦,这假设您不需要更改指向的对象,在这种情况下,引用将不再是选项


    话虽如此,在特定情况下可能会有其他选择。如果null和not null案例之间没有共享代码,那么最好使用重载函数,例如
    void foo(const std::string&)
    void foo()
    。这需要权衡各种权衡,包括API的一致性。选择最适合您的情况。

    正如您特别要求的一些STD库奇迹:STD库实际上具有一个
    参考包装器
    ,可以与
    可选
    组合,在一定程度上实现您的要求:

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

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

    FWIW,
    boost::optional
    支持引用,但
    std::optional
    不支持引用。那是你期待的地方。std::可选有什么问题?它为您提供了所需的确切功能want@chris虽然我知道Boost作为第二标准库的地位,我试图避免使用它,除非我需要的不仅仅是
    boost::optional
    @GabrielAlexander它不支持引用,因为它们不可
    破坏
    ,我不是建议你需要使用boost,它只是提供了一个很好的对比点,特别是考虑到Boost的一般标准化历史。我认为带有x和指向x的指针的函数变体是一个很大的禁忌,因为它们使用了
    &
    *
    模糊和混乱。@TopSekret,我无法理解这句话。你在说什么?如果
    f(x)
    f(&x)
    都是完全有效的,并且
    f(y)
    f(*y)
    都是完全有效的,人们可能会开始忘记
    x
    string
    还是
    string*
    ,对于
    y
    也是一样。我同意,如果函数的名称与
    T&
    T*
    的名称相同,这会造成混淆。这会增加认知负荷,尤其是当您的调用站点出于任何原因已经要求取消引用时。为什么引用会过载?重点是什么?最后一段是我今天听到的最明智的回答,因为我从来都不善于把宝贵的时间花在更重要的事情上;)您的方法中的
    NULL\u字符串
    常量应该是每个函数、每个库还是每个环境?@TopSekret我很可能是每个库,但它取决于太多的东西,无法概括地说。不管怎样,这总是有点像黑客
    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