как написать чистый блок try / catch, когда функция find возвращает ссылку?

#c #reference #try-catch

#c #ссылка #try-catch

Вопрос:

У меня есть некоторый код, который выглядит следующим образом:

 const SomeType amp; elt = x.find();
try {
    // ... do something with elt ...
} catch (...) {
    // handle processing exception
}
  

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

То, что я хотел бы написать, было бы

 try {
    const SomeType amp; elt = x.find();
} catch (...) {
    // handle "not found" exception
    return;
}
try {
    // ... do something with elt ...
} catch (...) {
    // handle processing exception
}
  

Но, конечно, это не работает, потому elt что к моменту достижения его блока обработки он уже не находится в области видимости. Я не могу изменить это на

 const SomeType amp; e<
try {
    elt = x.find();
} catch (...) {
    // handle "not found" exception
    return;
}
  

потому что, конечно, ссылочные типы в C не могут быть неинициализированы. Итак, у меня остались варианты временной настройки elt в качестве ссылки на фиктивный объект типа SomeType , чего я бы предпочел не делать, или вложения блоков try / catch, например:

 try {
    const SomeType amp; elt = x.find();
    try {
        // ... do something with elt ...
    } catch (...) {
        // handle processing exception
    }
} catch (...) {
    // handle "not found" exception
}
  

Мне это тоже не нравится: вложенность сбивает с толку, и мне не нравится, как обработчик исключений «не найден» скрывается там в конце.

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

Комментарии:

1. И именно поэтому я ненавижу функции поиска, которые завершаются через исключение, когда не найдены. Если бы вместо этого функция вернула a std::pair<bool, pointer_to_object> , вы могли бы зафиксировать это с помощью структурированной привязки, и тогда ваша проверка not found была бы оператором if сразу после вызова find . Есть ли у вас какой-либо контроль над интерфейсом find ?

2. Поскольку логически невозможно » // … сделайте что-нибудь с elt …» если find() генерируется исключение, какой бы способ вы ни хотели обработать это исключение, он должен логически отличаться от того, что делает ваш существующий try / catch , прежде чем возобновить работу с остальной частью кода, который использует ссылку. Поэтому вам просто нужен другой блок try / catch вокруг этого , чтобы выполнить любую очистку, которую необходимо выполнить, если find() возникает исключение.

3. @NathanOliver Они мне тоже не нравятся. У меня есть контроль над остальной частью кода, хотя я не уверен, что решу использовать его в этом случае.

Ответ №1:

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

 try {
    const SomeTypeamp; elt = x.find();
    // ... do something with elt ...
} catch (const NotFoundExceptionamp;) {
    // handle "not found" exception
} catch (...) {
    // handle processing exception
}
  

в противном случае повторная привязка ссылки не допускается, но может быть смоделирована указателем или std::reference_wrapper вообще,

И необязательная ссылка (не разрешена ни в std, но boost разрешает ее) может быть смоделирована указателем или std::optional<std::reference_wrapper<T>> .

Итак:

 const SomeType* eltPtr = nullptr; 
try {
    eltPtr = amp;x.find();
} catch (const NotFoundExceptionamp;) {
    // handle "not found" exception
    return;
}
const SomeTypeamp; elt = *eltPtr; 
try {
    // ... do something with elt ...
} catch (...) {
    // handle processing exception
}
  

Комментарии:

1. ДА. Я думаю, что первая часть вашего ответа — правильное решение. Я не могу использовать его напрямую (потому что, увы, исключения не отличаются), но они должны быть.

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

3. @SteveSummit » увы, исключения не отличаются » — тогда как бы вы различали их для начала? Есть ли у них какие-либо данные-члены , которые отличаются в зависимости от обстоятельств?

4. @SteveSummit: владение указателями плохо, указатели, не являющиеся владельцами, в порядке (не обязательно лучший способ)

5. @RemyLebeau Поскольку исключения структурированы так, как они есть в настоящее время, единственный способ отличить их — это отдельные блоки try / catch, один из которых охватывает код поиска, а второй — обработку.

Ответ №2:

Разделить на другую функцию:

 void func1()
{
    try {
        const SomeType amp; elt = x.find();
        func2(elt);
    } catch (...) {
        // handle "not found" exception
    }
}

void funct2(const SomeType amp; elt)
{
    try {
        // ... do something with elt ...
    } catch (...) {
        // handle processing exception
    }
}
  

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

Комментарии:

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

2. @SteveSummit Почему обработка исключительных материалов не обрабатывается деструкторами?

3. Я думаю, что ответ на ваш вопрос таков: «Потому что ни я, ни эта устаревшая кодовая база на самом деле не обернули наши головы вокруг RAII».

Ответ №3:

Это будет не всем по вкусу (поведение определяется кстати), но вы могли бы использовать

 #include <functional>
std::reference_wrapper<const SomeType> elt = e<
try {
    elt = x.find();
} catch (...) {
    // handle "not found" exception
    return;
}
  

Возможно, параметр elt to сам по себе является злоупотреблением std::reference_wrapper , конструкторы по умолчанию и перемещения удалены по замыслу: я обхожу это.

По сути std::refernce_wrapper , это указатель под капотом, но вы можете его повторно привязать, что, по сути, и есть то, что вы хотите сделать здесь.

Комментарии:

1. Я бы никогда не подумал об этом! Я, конечно, согласен с тем, что это «не всем по вкусу» (действительно, в этом случае я бы сказал, что это лекарство хуже, чем болезнь), но я рад узнать о технике, поэтому спасибо за публикацию.

2. Вы уверены, что самоинициализация допустима? (элемент должен быть скопирован, поэтому считывание неинициализированного значения -> UB, как int n = n; ).