У вас есть несколько вариантов с различными компромиссами.
1. Исключение. Выбросьте исключение в случае ошибки. Исключения не должны попадать непосредственно на сайт вызова, но их можно обработать в иерархии вызовов, где у вас больше контекста для устранения ошибки. Я бы даже сказал, что если вы всегда включаете вызов функции в блоки try-catch, вы можете неправильно понять преимущество исключений.
int func(const std::string& s);
2. Дополнительное возвращаемое значение.std::optional
, доступный на C++ 17 (или ранее как boost::optional
или техническая спецификация (TS)). Это явно указывает, что не возвращать значение является допустимым случаем, который следует учитывать при вызове функции.
std::optional<int> func(const std::string& s);
Отличная вещь, явный оператор BOOL, что позволяет этой идиомы:
if (std::optional<int> i = func("hello")) // or even auto
use(*i);
3. Указатель возвращаемое значение. С семантикой, аналогичной предыдущей точке, это может потребоваться, когда возвращается сохраненный элемент, например. в поиске. Если он не найден, возвращается nullptr
. В отличие от std::optional
, это имеет тот недостаток, что для этого требуется управление памятью/пожизненным временем (либо объект гарантированно переживет вызов, либо он распределяется динамически). Никогда не возвращайте указатель, который должен быть явно удален вызывающим, если вам действительно нужно использовать динамическое распределение std::unique_ptr
. Имейте в виду, что динамическое распределение дорогостоящее и почти всегда переполняется для небольших объектов, таких как int
. Кроме того, вам нужно подумать о const-correctness.
int* func(const std::string& s);
const int* func(const std::string& s);
std::unique_ptr<int> func(std::string& s);
4. Проверка-и-выход-параметр идиомы. Имейте функцию, которая принимает выходной параметр. Если он генерирует значение, он записывает результат в этот параметр и возвращает true, иначе он возвращает false.
bool func(int& out);
Это позволяет делать вызовы следующим образом.Недостатком является то, что вы не можете напрямую инициализировать объект (и, следовательно, не можете использовать const
-qualified, созданные по умолчанию или не назначаемые объекты, например).
int value;
if (func(value))
use(value);
5. Утверждение. Это то, что многие люди упускают из виду, особенно те, кто приходит из таких языков, как Java, где большинство ошибок категорически обрабатываются исключениями. Если единственным случаем, когда неверное значение будет возвращено, является неправильное обращение к функции - то есть логическая ошибка (не говоря уже об ошибке) в вашей программе - тогда часто бывает целесообразнее сделать это узнаваемым как можно скорее. Отладчики немедленно остановятся при неудачных утверждениях, в то время как ваш продуктивный код будет полностью оптимизирован.
int func(const std::string& s);
Утверждения являются замечательными, чтобы проверить выполнение или выполнение постусловий функций. Они особенно полезны для сигнализации ошибок, которые не могут быть осмысленно обработаны во время выполнения. Если они терпят неудачу, это означает, что ваша программная логика нарушена, и поскольку это не должно было произойти в первую очередь, нет хорошего способа оправиться от ситуации. Поскольку ваше приложение находится в неожиданном состоянии, выполнение дополнительного кода может ухудшить ситуацию.
Невозможно. Конструкция вашей функции ошибочна. – juanchopanza
Почему бы просто не выбросить исключение, если строка не представляет число? – Mureinik
Это показывает, почему обработка ошибок всегда сложна. В общем случае любая функция имеет два выхода: (1) значение успеха/отказа и (2) возвращаемое значение. Всегда заманчиво пытаться объединить эти два, потому что возвращение двух вещей по отдельности - это неприятность, это никогда не так удобно. Но если вы объедините два (как вы пытаетесь здесь), и если набор нормальных значений возврата не отличается от различия успеха/ошибки, вы получите неразрешимую двусмысленность. –