Злоупотребляю ли я std :: по желанию - PullRequest
1 голос
/ 20 июня 2019

Как std::optional вписывается в мой код?

Мой код содержит МНОГИЕ функции, которые выглядят примерно так:

bool GetName(_Out_ string& strRes)
{
   // try to get/calculate the value
   // value may also be empty
   // return true on success, false otherwise.
}

Теперь, когда я выяснил std :: необязательный, я просто пишу код:

std::optional<std::string> GetName()

Почему или когда не стоит использовать std::optional?

  1. Я понимаю, что использование std::optional сопряжено с ценой производительности и дляради аргумента, давайте предположим, что я готов заплатить.
  2. Я также понимаю, что кодировать что-то вроде: std::optional<int> iVal = 9; бессмысленно.

Я чрезмерно очарован std::optional?

Я нахожу std::optional настолько естественным, что это привело меня к выводу, что по умолчанию , многие из моих нативных типов, bool, int, string, должен быть объявлен как std::optional.

Таким образом, вместо следующих условий:

Используйте std::optional только , когда это абсолютно необходимо

Я склоняюсь к:

Используйте std::optional всегда , если вы не уверены, что это не нужно ни сейчас, ни в будущем.

Вопрос 1

Действует ли следующее правило:

использовать std::optional, когда:

  1. значение вычисляется во время выполнения
  2. расчет может завершиться неудачей
  3. вы хотите знать, если расчет не удался

Вопрос 2

Если использование std::optional становится обильным, рискуете ли я проблемами с читаемостью кода?


Обновление

Ответы и комментарии немного отодвинули обсуждение от моей первоначальной задачи, которая заключается в попытке определить практическое правило, когда следует использовать std::optional.

Я рассмотрю некоторые общие отзывы, которые я получил:

Вы кодируете функции проверки ошибок в стиле C.

используйте исключения, когда вычисление не удается.

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

Я не утверждал, что если GetName возвращает falseэто подразумевает какую-то ошибку в моем потоке.Когда GetName возвращает false, я могу либо сгенерировать случайное имя, либо просто проигнорировать текущую итерацию.Независимо от того, как вызывающие абоненты реагируют на возвращаемое значение, name равно optional в том смысле, что оно may not be present.

. Я могу привести лучший пример:

void GetValuesFromDB(vector<string> vecKeys, map<string,string> mapKeyValue);

inэта функция "вынуждена" предоставить два контейнера, так как я хочу различать:

  1. ключ найден в БД, и он пуст.этот ключ будет в mapKeyValue
  2. ключ не найден в БД.,этот ключ НЕ будет в mapKeyValue

Но с optional я мог бы пойти:

void GetValuesFromDB(map<string,std::optional<string> > mapKeyValue);

Я считаю, что мы принимаем слово optional слишком буквально.

Я действительно поддерживаю идею, что можно использовать std::optional Если вы хотите красиво представить обнуляемый тип.Вместо того, чтобы использовать уникальные значения (например, -1, nullptr, NO_VALUE или что-то еще)

Комитет по стандартизации c ++ мог бы легко решить назвать std::optional, std::nullable.

Ответы [ 3 ]

3 голосов
/ 20 июня 2019

Злоупотребляю ли я std :: необязательно

Может быть.

Предполагаемая семантика std::optional должна указывать, что что-то является необязательным . То есть, если вещь не присутствует, это нормально, а не ошибка.

Если ваш метод дает сбой, он возвращает пустой необязательный параметр, указывающий на сбой. Но является ли этот сбой также ошибкой , зависит от семантики, ожидаемой вызывающим кодом, который вы не показали или не описали: что вы делаете, если необязательный результат пуст

Например,

auto name = GetName();
if (!name) { diediedie(FATAL_ERROR); }

предполагает, что имя не является обязательным. Если вы не можете (как вы говорите) использовать исключения здесь, подход variant<string, error> кажется более понятным и также позволяет вашей отказавшей функции сообщать причину сбоя, которая в противном случае могла бы перейти в исключение.

Обратно

auto name = GetName();
if (name) {
  cout << "User is called " << *name << '\n';
} else {
  cout << "User is anonymous\n";
}

кажется разумным, потому что неудача при получении имени не ошибка.


Почему ... не для использования std::optional?

Когда вы не хотите сообщать, что-то необязательно.

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

Я слишком очарован ...?

Может быть. Если ваша обработка ошибок загрязняет весь ваш код, вам, вероятно, следует рассмотреть возможность более раннего сбоя, а не натыкаться на логику, которая никогда не будет успешной.

Если у вас действительно есть много переменных, которые по закону могут отсутствовать - с точки зрения логики вашей программы - тогда вы, вероятно, используете это правильно.

Вопрос 1

Опять же, это зависит от того, что означает сбой в контексте вашей программы.

Вопрос 2

Да, ваш код будет нечитаемым беспорядком, если все является необязательным, частично из-за визуального шума, но в основном потому, что вы никогда не можете быть уверены, какие переменные существуют даже в данной точке вашей логики.


Ваша GetValuesFromDB правка , вероятно хорошо, для использования необязательно , если ваша база данных относительно неструктурирована и не имеет ограничений. Вы не можете зависеть от того, какой конкретный столбец заполняется, все необязательно, вам нужно вручную проверять наличие каждого поля. Они действительно необязательны.

Если ваша БД сильно структурирована, может не подойти для использования необязательного, скажем, первичного ключа. Вы не можете иметь строку без первичного ключа, поэтому сделать его необязательным кажется немного странным. Возможно, просто не стоит иметь отдельный интерфейс для нескольких обязательных столбцов.

Я полагаю, что мы слишком буквально понимаем слово "необязательный".

Я искренне не могу сказать, читали ли вы мой ответ - ваш комментарий говорит о том, что вы его не поняли, по крайней мере.

Допустим, мы называем это обнуляемым вместо этого. Я бы все же предположил бы, что возвращение nullable<T> для чего-то, что на законных основаниях может не существовать, это хорошо, но возвращение того же типа для чего-то, чье отсутствие указывает на ошибку, является неправильным.

Вы должны решить, что является ошибкой для вашего кода. Мы не можем сделать это для вас, особенно с предоставленной информацией. Так что вы должны использовать необязательно для всего? Может быть. Что я и сказал. Определите семантику и предварительные условия вашего собственного кода, и вы будете знать, какие элементы являются необязательными, какие являются обязательными, и чье отсутствие следует обрабатывать как ошибку вместо обычного пути к коду.

1 голос
/ 20 июня 2019

std::optional - это класс, который представляет «необязательное» значение. Если у вас есть случай, когда вы можете создать значение, а можете и нет, то необязательно использовать этот вариант. Цена исполнения незначительна (дополнительное условие, если значение здесь может на самом деле замедлить ход событий, но поскольку значение необязательно, вам всегда будет где-то проверять, присутствует ли значение в любом случае), реальная цена - память:

printf("%d\n", (int)sizeof(std::string));
printf("%d\n", (int)sizeof(std::optional<std::string>));

печать:

32
40

на Clang 64 бит.

Так что, если вы делаете в - скажем, структура:

struct A {
    std::optional<std::string> a, b, c, d, e, f, g, h;
};

struct B {
    std::string a, b, c, d, e, f, g, h;
    unsigned char has_a : 1,
        has_b : 1,
        has_c : 1,
        has_d : 1,
        has_e : 1,
        has_f : 1,
        has_g : 1,
        has_h : 1;
};

тогда вы получите sizeofs:

A - 320
B - 264

что может на самом деле причинить боль. Но обычно нет.

Так вы злоупотребляете факультативно? Если вы используете необязательный параметр для сообщения об ошибке, то, возможно, слегка да, в зависимости от контекста ошибки. Но это всегда зависит. Если для ясности кода важнее сигнализировать, то эта функция не выдает значение вместо , почему не выдает значение, тогда, конечно, продолжайте.

0 голосов
/ 20 июня 2019

Я бы сказал, что это оскорбление.

Я бы переписал на std::string getName() const (полагаясь на требуемый сейчас RVO), и если что-то не получится (например, какая-то ошибка при извлечении), я бы выдал исключение (пользовательского типа, полученного из std::exception). Вопрос о том, является ли пустое имя ошибкой, зависит от вашего приложения.

Если вам не разрешено использовать исключения, полагайтесь на распространение ошибок в стиле C в форме

int getName(std::string*) const

при использовании std::optional делает фактическую ошибку непрозрачной (если только вы не напишите ошибку struct в текущем потоке).

...