C ++ идиоматический способ для обнуляемой ссылки - PullRequest
3 голосов
/ 06 июля 2019

Я пишу функцию, которая должна принимать const std::string &, но также и специальное значение nullptr. В настоящее время я использую const std::string *. Тем не менее, я чувствую, что должен быть более простой способ выполнения этой задачи на C ++.

Мой самый сильный контрапункт проходит по значению (строка не будет очень длинной, но 64 КБ - это не то, что я хочу копировать). Это также подразумевает, что мне не нужен объект типа optional<T>, который имел бы поле значения типа T. Кроме того, я не хочу использовать какие-либо внешние библиотеки. Мой проект не использует Boost или GSL, просто C ++ (точнее C ++ 17).

Существует ли стандартный библиотечный класс (чудеса происходят в namespace std) или широко распространенная идиома для такой ситуации?

Ответы [ 5 ]

6 голосов
/ 06 июля 2019

Если бы std::optional поддерживал ссылки, это был бы модный "современный" способ сделать это.

Но:

  • это не так, и
  • иногда быть причудливым и «современным» - это еще не все, что нужно сделать.

Старое, проверенное и понятное решение - принятьуказательЭтот подход делает абсолютно все, что вам нужно.

Просто сделайте это, а затем продолжайте тратить свое драгоценное время на более важные вопросы!

3 голосов
/ 07 июля 2019

Интересным аспектом этого вопроса является то, что я чаще видел обратную перспективу: как в C ++ можно указывать указатель, который не может быть нулевым? Ответы сводятся к одним и тем же принципам.

Основные отличия указателя от ссылки:

  1. Можно сделать указатель для указания на другой объект.
  2. Указатель может быть нулевым.

Если вы сделаете указатель постоянным, первое отличие исчезнет. C ++ способ иметь «обнуляемую ссылку» - это иметь постоянный указатель. Обратите внимание, что я говорю о том, что указатель является константой, как в T * const, который не зависит от константы указываемого объекта, как в T const * или const T *.

Вас, возможно, учили думать, что все указатели являются злом, но это чрезмерное обобщение. Необработанные указатели - хороший способ ссылаться на необязательные объекты , память которых вы не ответственны за . Если вы несете ответственность за освобождение памяти, используйте умный указатель, но если кто-то другой хранит память для вас, используйте ссылку или указатель. Используйте ссылку, если объект требуется (не может быть нулевым); указатель, если он необязательный (может быть нулевым). О, это предполагает, что вам не нужно менять, на какой объект указывает указатель, и в этом случае ссылка больше не будет опцией.

При этом могут быть альтернативы в конкретных контекстах. Если между нулевым и ненулевым случаями нет общего кода, может быть лучше иметь перегруженные функции, например, void foo(const std::string &) и void foo(). Это должно быть сбалансировано с различными компромиссами, включая согласованность API. Пойдите с тем, что лучше всего подходит для вашей ситуации.

1 голос
/ 07 июля 2019

Как вы конкретно просили о каком-то чуде библиотеки 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

Сказав это, я также не думаю, что это хорошая идея;) (см. Принятый ответ).

1 голос
/ 06 июля 2019

Альтернативный подход заключается в предоставлении пустой строки, которую ваша функция может обнаружить с помощью проверки идентичности объекта:

#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);
}

Однако можно подумать, что это пустая трата пространства, и я не считаю это особенно идиоматичным. Тем не менее, я иногда использовал подобный подход в коде частной библиотеки (особенно в сочетании с аргументами по умолчанию).

0 голосов
/ 06 июля 2019

Напишите две функции:

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

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