Использование throw для замены return в C ++ не пустых функциях - PullRequest
0 голосов
/ 15 февраля 2019

В функциях C ++ целесообразно ли заменять return на throw?Например, у меня есть следующий код

// return indices of two numbers whose sum is equal to target
vector<int> twoSum(vector<int>& nums, int target) {
    for(int i=0; i<nums.size()-1; ++i)
        for(int j=i+1; j<nums.size(); ++j)
        {
            if(nums[i] + nums[j] == target) return vector<int>{i, j};
        }
    // return vector<int>{};
    throw "no solution";
}

Приведенный выше код компилируется с моим GCC 7.2.

Ответы [ 10 ]

0 голосов
/ 22 февраля 2019

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

  • return используется для возврата регулярных результатов функций.
  • throw является предпочтительным методом реагирования на условия ошибки, когда значение результата несуществовать.

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

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

В вашем примере ответ зависит от семантики функции:

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

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

0 голосов
/ 16 февраля 2019

Ваш вопрос не зависит от языка.

Возврат и Бросок имеют разные цели.

  • Возврат предназначен для возврата значения;while,
  • Бросок для выброса исключения;и
    • Исключение должно быть выдано в действительно исключительных условиях.

Бонусное содержание (из Полного кода Стива Макконнелла)2)

Вот все доступные вам альтернативы, когда вы сталкиваетесь с ситуацией, когда вы не можете нормально вернуться:

  • Возвращает нейтральное значение , например, -1 изметод, который должен возвращать счет чего-либо;
  • Возвращать ближайшее цифровое значение , например, 0, на цифровом спидометре автомобиля, когда он движется задним ходом;
  • Возвращает то же значение, что и предыдущий вызов , как показание температуры на термометре, когда вы читаете каждую секунду, и вы знаете, что показания не отличаются резко за такой короткий интервал;
  • Возвратследующий допустимый фрагмент данных , например, когда вы читаете строки из таблицы, а определенная строка повреждена;
  • Установить / вернуть статус ошибки / код / ​​объект встречается довольно частов вызовах библиотеки C ++;
  • Отображать сообщение в окне предупреждения , но будьте осторожны, чтобы не выдавать слишком много информации, которая может помочь злоумышленнику;
  • Журналв файл ;
  • Бросить исключение , как вы думаете;
  • Вызвать центральный обработчик ошибок / подпрограмму , если этокак устроена ваша система;
  • Выключение .

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

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

0 голосов
/ 16 февраля 2019

То, что функция выдает последний вызов, не означает, что она заменяет return.Это просто поток логики.

Вопрос не должен быть:

Это хорошая практика, чтобы заменить возврат броском?

Вместо этого должно быть примерно: как определить ваш API и контракты .

Если вы хотите гарантировать пользователям функции, что vector никогда не будет пустым, то генерировать исключение - хорошая идея.

Если вы хотите гарантировать, что ваша функция не выбрасывает исключения, а возвращает пустое значение vector при определенных условиях, тогда выбрасывание - плохая идея.

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

0 голосов
/ 15 февраля 2019

Нет, throw не является хорошей семантической заменой для return.Вы хотите использовать throw только в том случае, если ваш код сделал что-то, чего не следует делать, а не для обозначения совершенно допустимого отрицательного результата функции.

Как правило, исключения означают, когдаво время выполнения вашего кода произошло что-то ненормальное или неожиданное.Глядя на назначение вашей функции, появление двух целых чисел в переданном векторном суммировании в target является очень возможным результатом функции, так что результат не является ненормальным и поэтому не должен рассматриваться как исключительный.

0 голосов
/ 16 февраля 2019

Функция должна выдавать исключение, когда она не может выполнить свое постусловие.(Некоторые функции могут также генерировать исключения, когда их предварительные условия не выполняются; это другая тема, в которую я не буду здесь вдаваться.) Поэтому, если ваша функция должна вернуть пару целых чисел из вектора, суммирующего весли у данной цели нет другого выбора, кроме как создать исключение, если оно не может быть найдено.Но если контракт функции позволяет ей возвращать значение, указывающее, что он не смог найти такую ​​пару, то он не должен выдавать исключение, поскольку он может выполнить контракт.

В вашем случае:

  • Вы можете заставить вашу функцию возвращать пустой вектор, когда она не может найти два целых числа, суммирующих с заданной целью.Тогда ему не нужно выдавать исключение.
  • Или вы можете заставить свою функцию возвращать std::optional<std::pair<int, int>>.Ему никогда не нужно выбрасывать исключение, потому что он может просто вернуть пустой необязательный аргумент, когда не может найти подходящую пару.
  • Если, однако, вы заставите его вернуть std::pair<int, int>, то он должен выбросить исключение, потому чтонет разумного значения, которое нужно вернуть, если не удается найти подходящую пару.

Как правило, программисты на C ++ предпочитают писать функции, которым не нужно генерировать исключения, чтобы сообщать о простых «разочарованиях»такие как сбои поиска, которые легко предвидятся и обрабатываются локально.Преимущества возврата значений, а не выдачи исключений, широко обсуждаются в других местах, поэтому я не буду перефразировать это обсуждение здесь.

Таким образом, объявление «сбой» путем выброса исключения обычно ограничивается следующими случаями:

  • Функция является конструктором, и она просто не может установить инвариант своего класса.Он должен генерировать исключение, чтобы гарантировать, что вызывающий код не видит сломанный объект.
  • Возникает «исключительное» условие, которое должно обрабатываться на более высоком уровне, чем у вызывающего.Звонящий, скорее всего, не знает, как оправиться от состояния.В таких случаях использование механизма исключения освобождает вызывающего абонента от необходимости выяснять, как действовать, когда возникает исключительное условие.
0 голосов
/ 15 февраля 2019

Понятия этого ответа взяты из языка программирования C ++ Бьярном Страуструпом.

КОРОТКИЙ ОТВЕТ

Да, исключение можно использовать в качестве метода возвращаемого значения.Примером является следующее для функции поиска двоичного дерева:

void fnd(Tree∗ p, const string& s)
{
    if (s == p−>str) throw p; // found s
    if (p−>left) fnd(p−>left,s);
    if (p−>right) fnd(p−>right,s);
}


Tree∗ find(Tree∗ p, const string& s)
{
    try {
       fnd(p,s);
    }
    catch (Tree∗ q) {
        // q->str==s
        return q;
    }
    return 0;
}

Однако этого следует избегать, поскольку:

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

Кроме того, существуют дополнительные ограничения:

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

Более длинный ответ

Исключением является объект, выданный для представления возникновения ошибки.Это может быть любой тип, который может быть скопирован, но настоятельно рекомендуется использовать только определенные пользователем типы, специально определенные для этой цели.Исключения позволяют программисту явно отделять код обработки ошибок от «обычного кода», делая программу более читабельной.

Прежде всего, исключения предназначены для управления синхронными событиями , а не асинхронными.Это одно из первых ограничений.

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

Это имеет некоторое очарование, но следует избегать , поскольку это может привести к путанице и неэффективности.Страуструп предлагает:

Когда вообще возможно придерживаться представления «обработка исключений - обработка ошибок».Когда это сделано, код делится на две категории: обычный код и код обработки ошибок.Это делает код более понятным.Кроме того, реализации механизмов исключения оптимизируются на основе предположения, что эта простая модель лежит в основе использования исключения.

Поэтому в принципе следует избегать использования исключений для возвращаемого значения, поскольку

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

Существуют программы, которые по практическим или историческим причинам не могут использовать исключения (никак обработка ошибок, так даже меньше):

  • Критически важный по времени компонент встроенной системы, в котором работа должна быть гарантированно завершена в указанное максимальное время.В отсутствие инструментов, которые могут точно оценить максимальное время распространения исключения от throw до catch, должны использоваться альтернативные методы обработки ошибок.
  • Большая старая программа, в которой управление ресурсами является рекламой.hoc mess (бесплатное хранилище бессистемно управляется с использованием открытых указателей, news и delete), а не полагается на какую-то систематическую схему, такую ​​как дескрипторы ресурсов (string s vector s).

В вышеупомянутых случаях предпочтительны традиционные методы перед исключением.

0 голосов
/ 15 февраля 2019

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

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

0 голосов
/ 15 февраля 2019

То, что вы упомянули, не является хорошей практикой программирования.Замена оператора return на throw недопустима в коде производственного уровня, особенно на платформах автоматизированного тестирования, которые генерируют списки всех исключений как способ подтверждения или опровержения определенной функциональности.При разработке кода вы должны учитывать потребности тестировщиков программного обеспечения.throw просто не взаимозаменяем с return.throw используется для указания того, что в программе ошибка произошла непредвиденная ситуация.return используется для оповещения о завершении метода.Обычно для передачи кодов ошибок используется return, но возвращаемые значения не приводят к прерыванию программы так же, как throw.Кроме того, throw может завершить программу, если она не обрабатывается правильно.

По сути, рекомендуется использовать throw, когда в методе обнаружена значительная ошибка, но всегда следуетбыть чистым возвращением, если такая ошибка не обнаружена. Может ли сделать такую ​​замену?Может быть, и, возможно, это будет работать логически ... но это не очень хорошая практика и, конечно, не популярная реализация.

0 голосов
/ 15 февраля 2019

В функциях C ++ рекомендуется заменить return на throw?

Return не может быть заменен броском в общем случае .

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

Является ли это «хорошей практикой», а какой случай «исключительным»субъективны.Например, для такой функции поиска, как ваша, неудивительно, что не может быть решения, и я бы сказал, что бросание не подходит.

Часто есть и другие альтернативы бросанию.Сравните ваш алгоритм с чем-то вроде std::string::find, который возвращает индекс начала подстроки.В случае, когда подстрока не существует, она возвращает «не значение» std::string::npos.Вы можете сделать то же самое и решить, что индекс -1 возвращается, когда результат не найден.Существует также общий способ добавления незначного представления к типу в случаях, когда ни одно из существующих представлений не может быть зарезервировано для этой цели: std::optional.

PS Вектор, вероятно, не является хорошим выбором длявозвращая пару чисел.std::pair может быть лучше, или пользовательский класс, если у вас есть хорошие имена для чисел.

0 голосов
/ 15 февраля 2019

return и throw имеют две разные цели и не должны рассматриваться как взаимозаменяемые.Используйте return, когда у вас есть действительный результат для отправки обратно вызывающему.С другой стороны, используйте throw, когда происходит какое-то исключительное поведение.Вы можете получить представление о том, как другие программисты используют throw, используя функции из стандартной библиотеки и отмечая, когда они генерируют исключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...