Короткий вариант этого вопроса заключается в том, как реализовать единственную функцию, которая возвращает как true
и false
, предоставляя детали при ошибке (false
)?
Скажем, я хочу создать статическую функцию remove()
, которая получит путь в виде string
и удалит ее из файловой системы.
Если операция удаления может привести к некоторым неожиданным ошибкам, я бы хотел вернуть статус.
Версия 1
static bool remove( const string& path );
Это вернет true
если путь был удален и false
противном случае.
Но что, если мне нужно больше деталей относительно отказа процесса удаления?
Версия 2
static void remove( const string& path );
Эта функция теперь вызывает какое-то exception
при сбое, которое в основном вернет причину, по которой удаление процесса завершилось неудачно. Это позволяет каждому вызывающему пользователю использовать try-catch
при вызове этой функции, которая может быть немного раздражающей и уродливой, если вы не заботитесь о результате.
Я пытаюсь создать чистую подпись, которая позволит мне объединить обе версии в одну. remove
функция является лишь одной из многих функций статической утилиты, поэтому я бы хотел избежать необходимости оставлять обе версии (хотя они и не являются переопределяемыми на данный момент).
Предложение 1:
static bool remove( const string& path, bool throw_on_fail );
Теперь вызывающий может чередовать обе версии функции, передавая булевский флаг.
Мне не нравится это предложение. Как я уже сказал, функция remove
является лишь одной из многих статических функций утилиты. Я не думаю, что добавление логического аргумента для каждой функции - такая хорошая идея.
Предложение 2:
static EResultCode remove( const string& path );
Здесь у нас есть enum
.
Это один немного лучше, но я уже вижу такие ошибки, как следующий, if
выражение, if remove("f1")
. получение int value 4 из remove()
не означает успеха.
Предложение 3:
static Result remove( const string& path );
Здесь у нас есть класс Result
который будет содержать как логическое, так и подробное описание.
Мне это кажется излишним.
Я просмотрел API общих интерфейсов библиотек c++
, wx & boost. не могли найти там подавляющего понимания.
Я пытаюсь придумать общую подпись для всех этих функций. куда бы вы пошли?
Я бы определенно пошел с подходом класса Result.
Игнорируя вопрос о том, являются ли исключения правильными инструментами для обработки ошибки, не найденной в файле, дополнительный параметр bool для включения или выключения будет сделать код клиента менее читаемым из-за всех истинных и ложных аргументов, смысл которых совершенно неясно, если читатель не обратится к документации remove() или, по крайней мере, к ее подписи:
remove("file.txt", true); // what true?!
Я бы также воздержался от ссылок на ошибки. Это так расстраивает, когда вы (клиент функции) вынуждены использовать дополнительные локальные переменные, которые вам даже не нужны позже. Очень С++ - в отличие от. Этот подход в конечном итоге приведет к большому количеству клиентских кодов, подобных этому:
Error dummy;
remove("file.txt", dummy);
Подход класса Result означает, что клиентам придется вводить немного больше, если им нужно знать детали ошибки, но их код станет намного яснее и читабельным в результате этого. Ваша забота о том, что это может наложить дополнительную нагрузку на клиентов, кажется необоснованной, по крайней мере, если я представляю себя клиентом remove() :)
suggestion 3
.
struct status {
std::string msg;
bool success;
status(): success(true) {}
status(std::string msg_): success(false), msg(msg_) {}
explicit operator bool() const { return success; }
};
static status const success;
status func1() { return true; }
status func2() { return success; }
status func3() { return status("something went wrong); }
if (func1()) { std::cout << "success!" << std::endl; }
if (func1().success) { std::cout << "success!" << std::endl; }
auto rc = func3();
if (!rc) { std::cout << "error" << rc.msg << std::endl; }
То, что я сделал в одном случае, это вернуть функцию char const*
с nullptr
для успеха и сообщение об ошибке для отказа. Тем не менее, в этом случае функции были extern "C"
, поэтому у меня не было почти так много альтернатив. В C++ я бы, вероятно, определил класс и вернул его. Класс может иметь неявное преобразование в bool
если вы хотите, но я не уверен, что это хорошая идея; У меня была бы succeeded
функция, которая вернула bool
: это намного яснее.
Обратите внимание, что даже в этом случае вам нужно сохранить возвращаемое значение в локальную переменную, чтобы дополнительная информация оставалась после обнаружения сбоя; вы не можете просто написать:
if ( remove( path ) ) {
// error
} else {
// success
}
но нужно написать:
Results results = remove( path );
if ( !results ) {
// error, do something with results.message()
} else {
// success
}
Также довольно болезненно, почти столько же, сколько попытка поймать.
Если у вас есть функция remove()
, и целью этих целей является удаление вещей из файловой системы, мы должны ожидать, что она будет работать в обычном случае.
В случае, когда по какой-то причине невозможно работать (существует множество причин, по которым это может потерпеть неудачу), мы должны рассматривать это как исключение из нормального рабочего случая.
Я бы предложил:
void remove() {...}
И, называя это:
try
{
remove("/home/olduser");
}
catch(std::runtime_error& e)
{
std::cerr << "Failed to remove: " << e.what() << '\n';
}
Исключения являются частью языка по какой-либо причине, используют их.
Вы сказали, что смотрели на boost
(и другие) для вдохновения. Посмотрите на реализацию boost::asio
. Почти у всех функций есть две перегрузки, одна из которых принимает код ошибки для отчета, и тот, который имеет такое же поведение, но просто выдает код ошибки как исключение в случае сбоя.
Предоставление обоих может быть чрезмерным. Мое предпочтение заключается в том, чтобы полагаться на обработку исключений, поскольку оно специально предназначено для обработки этих типов ситуаций.
filename
, тогда ошибка является исключительной и может рассматриваться как исключение. Если требования состоят в том, что функция завершается ошибкой, если нет файла с таким именем для начала, и имя функции исходит от пользователя ... Нет ничего исключительного в том, что пользователь неправильно вводит имя файла.
Вы можете использовать что-то вроде
static bool remove( const string& path, tStatus& myStatus );
И определите tStatus как любой тип, который вы хотите возвращать как ошибки. Может быть так же просто, как typedef int tStatus;
tStatus
может содержать значение, указывающее на успех. нет необходимости возвращать bool, а также. поэтому мы вернулись в suggestion 2
if(!function(foo, bar, status)) return status;
И если ваша структура причины ошибки в конечном итоге окажется большим классом, содержащим строки, предложение 2 также не будет работать.
Вы также можете позволить функции возвращать bool, а среди параметров вы передаете ссылку на структуру, которая может содержать причину. Как это:
bool remove(const string& path, FailReason* fr){
//if the FailReason is a null pointer we don't fill it
If(fr != 0);
}
Вы можете передать null в структуре fail
Вы можете использовать:
static bool remove(const string& path, std::nothrow_t);
static void remove(const string& path);