создать сигнатуру функции, которая возвращает как истину, так и ложь, предоставляя детали при сбое

0

Короткий вариант этого вопроса заключается в том, как реализовать единственную функцию, которая возвращает как 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. не могли найти там подавляющего понимания.

Я пытаюсь придумать общую подпись для всех этих функций. куда бы вы пошли?

  • 0
    Можете ли вы дать более подробную информацию о том, почему предложение 3 излишне?
  • 0
    Я не могу понять, почему члены здесь так быстро отмечают этот вопрос как основанный на мнении.
Показать ещё 4 комментария
Теги:
return-value

7 ответов

2
Лучший ответ

Я бы определенно пошел с подходом класса Result.

Игнорируя вопрос о том, являются ли исключения правильными инструментами для обработки ошибки, не найденной в файле, дополнительный параметр bool для включения или выключения будет сделать код клиента менее читаемым из-за всех истинных и ложных аргументов, смысл которых совершенно неясно, если читатель не обратится к документации remove() или, по крайней мере, к ее подписи:

remove("file.txt", true); // what true?!

Я бы также воздержался от ссылок на ошибки. Это так расстраивает, когда вы (клиент функции) вынуждены использовать дополнительные локальные переменные, которые вам даже не нужны позже. Очень С++ - в отличие от. Этот подход в конечном итоге приведет к большому количеству клиентских кодов, подобных этому:

Error dummy;
remove("file.txt", dummy);

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

  • 0
    Это хороший ответ. Предполагая, что у нас нет другого способа атаковать это, мне также больше нравится suggestion 3 .
2
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; }
1

То, что я сделал в одном случае, это вернуть функцию 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
}

Также довольно болезненно, почти столько же, сколько попытка поймать.

1

Если у вас есть функция remove(), и целью этих целей является удаление вещей из файловой системы, мы должны ожидать, что она будет работать в обычном случае.

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

Я бы предложил:

void remove() {...}

И, называя это:

try
{
   remove("/home/olduser");
}
catch(std::runtime_error& e)
{
   std::cerr << "Failed to remove: " << e.what() << '\n';
}

Исключения являются частью языка по какой-либо причине, используют их.

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

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

  • 1
    Это зависит от требований. Если требования заключаются в том, что после функции отсутствует файл с именем filename , тогда ошибка является исключительной и может рассматриваться как исключение. Если требования состоят в том, что функция завершается ошибкой, если нет файла с таким именем для начала, и имя функции исходит от пользователя ... Нет ничего исключительного в том, что пользователь неправильно вводит имя файла.
0

Вы можете использовать что-то вроде

static bool remove( const string& path, tStatus& myStatus );

И определите tStatus как любой тип, который вы хотите возвращать как ошибки. Может быть так же просто, как typedef int tStatus;

  • 0
    tStatus может содержать значение, указывающее на успех. нет необходимости возвращать bool, а также. поэтому мы вернулись в suggestion 2
  • 0
    Что ж, я думал, что вы хотите вернуть bool, чтобы вам было проще делать такие выражения, как if(!function(foo, bar, status)) return status; И если ваша структура причины ошибки в конечном итоге окажется большим классом, содержащим строки, предложение 2 также не будет работать.
Показать ещё 2 комментария
0

Вы также можете позволить функции возвращать 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

  • 0
    NULL-ссылки не являются определенным c ++. Я думаю, что вы хотели использовать указатель?
  • 0
    Да, моя голова явно немного испорчена ...
0

Вы можете использовать:

static bool remove(const string& path, std::nothrow_t);
static void remove(const string& path);

Ещё вопросы

Сообщество Overcoder
Наверх
Меню