попытаться/поймать присваивание ссылки

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

try {
    Something &o = myMap.at(myIndex);
    foo(o);
} catch(std::out_of_range &e) {
    // If this was thrown by myMap.at, we can handle it
    // If this was thrown by foo, we can't, and need to propagate it
}

Итак, я хочу сделать что-то вроде этого:

Something &o;
try {
    o = myMap.at(myIndex);
} catch(std::out_of_range &e) {
    // Handle the error
}
foo(o);

Но, конечно, это недействительно. Я могу придумать способы обернуть исключения foo в другое исключение, а затем развернуть его вне try, но это довольно грязно. Есть ли способ лучше?

MCVE можно найти здесь: https://ideone.com/DJHxpO


person Dave    schedule 25.03.2017    source источник
comment
Я предполагаю, что foo тоже может бросить std::out_of_range? В противном случае это не было бы проблемой.   -  person Some programmer dude    schedule 25.03.2017
comment
@Someprogrammerdude в моем конкретном случае использования foo заранее неизвестно, поэтому я должен предположить, что он может выдать что угодно.   -  person Dave    schedule 25.03.2017
comment
Однако, если вы помните, что исключения (при выдаче) обходятся дорого в C++ и на самом деле не должны использоваться для проверки, может быть хорошей идеей, возможно, иметь отдельную проверку, существует ли ключ myIndex в карте в первую очередь.   -  person Some programmer dude    schedule 25.03.2017
comment
@Someprogrammerdude Я согласен, хотя похоже, что это не настоящая функция, которая что-то выдает. Будем надеяться, что настоящее исключение не является исключением, которое можно было бы легко предотвратить.   -  person chris    schedule 25.03.2017
comment
@Someprogrammerdude да, исключение в этом случае действительно исключительное, а не просто обычные ошибки проверки.   -  person Dave    schedule 25.03.2017


Ответы (4)


Вы можете использовать немедленно вызываемое лямбда-выражение:

Something &o = [&]() -> decltype(auto) {
    try {
        return myMap.at(myIndex);
    } catch(std::out_of_range &e) {
        // Handle the error
        // Return some other object for o to refer to.
    }
}();
foo(o);
person chris    schedule 25.03.2017
comment
Довольно многословно, но, похоже, это именно то, что я ищу. Спасибо! - person Dave; 25.03.2017
comment
@Dave, без ссылки было бы немного проще: [&] { ... }(). Я бы не назвал это подробным, учитывая, что все остальное — это код, который вы все равно пишете. Однако потребность в явном возвращаемом типе немного раздражает, и есть небольшой шанс, что однажды он может выглядеть как [&]() => { ... }(), хотя я бы больше надеялся, когда тело представляет собой простое выражение. - person chris; 25.03.2017

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

Something *o; // initialize it with nullptr if necessary
try {
    o = &myMap.at(myIndex);
} catch(std::out_of_range &e) {
    // Handle the error
}
foo(*o);      // check whether it's nullptr before dereference if necessary
person songyuanyao    schedule 25.03.2017
comment
Хорошая идея, но я предпочитаю решение Криса, так как оно сохраняет ненулевую семантику С++. Я предполагаю, что строка Something &useThisO = *o; сразу после попытки по крайней мере поместила бы это в одно место, но все еще существует риск того, что блок catch справится с ошибкой и забудет установить o, что приведет к ошибке времени выполнения. - person Dave; 25.03.2017

Я думаю, что это хороший вариант использования boost::optional (std:: в С++ 17).

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

Думаю, я бы выразился так:

int myIndex = 1;
foo(maybe_at(myMap, myIndex).value_or_eval([]()->Something& {
    // perform error code here
    return default_something;
}));

Полный пример кода здесь:

#include <map>
#include <type_traits>
#include <boost/optional.hpp>

template<class Container>
struct container_traits
{
    using maybe_const_type = std::remove_reference_t<Container>;
    using container_type = std::decay_t<Container>;
    using is_const = std::is_const<maybe_const_type>;
    using key_type = typename container_type::key_type;
    using raw_mapped_type = typename container_type::mapped_type;
    using mapped_type = std::conditional_t<is_const::value, std::add_const_t<raw_mapped_type>, raw_mapped_type>;
    using mapped_reference = std::add_lvalue_reference_t<mapped_type >;
};

template<class Container, class Key>
auto maybe_at(Container&& container, Key&& key)
{
    using traits = container_traits<Container>;
    using result_type = boost::optional<typename traits::mapped_reference>;

    auto result = result_type {};
    auto ifind = container.find(key);
    if (ifind != container.end()) {
        result = ifind->second;
    }
    return result;
}


struct Something {};

void foo(Something&) {}
void foo(const Something&) {}

std::map<int, Something> myMap1;
const std::map<int, Something> myMap2;

auto default_something = Something{};

int main() {

    int myIndex = 1;

    foo(maybe_at(myMap1, myIndex).value_or_eval([]()->Something& {
        // perform error code here
        return default_something;
    }));

    foo(maybe_at(myMap2, myIndex).value_or_eval([]()->Something const& {
        // perform error code here
        return default_something;
    }));

}
person Richard Hodges    schedule 25.03.2017
comment
tbh Я использовал map.at только как простой пример метода, который возвращает неконстантную ссылку и может генерировать. Я согласен с тем, что необязательный возвращаемый тип имеет больше смысла для этого конкретного случая, но я хотел только продемонстрировать абстрактную ситуацию без необходимости показывать дополнительный код. - person Dave; 25.03.2017
comment
@ Дэйв, конечно. Тем не менее, необязательный — это то, для чего предназначен этот вариант использования. - person Richard Hodges; 25.03.2017

Простым способом, который работает со всеми версиями C++, будет

bool mapped = false;
try {
    Something &o = myMap.at(myIndex);
    mapped = true;
    foo(o);
} catch(std::out_of_range &e) {
     if (mapped) throw;
     //   if we get to here, the exception was thrown by myMap.at()
}

Это также позволяет избежать необходимости ссылаться на какой-либо другой объект, если myMap.at() терпит неудачу.

person Peter    schedule 25.03.2017
comment
Это хороший способ справиться с этим, с тем преимуществом, что он позволяет обрабатывать больше типов ошибок, чем ответ Криса, и безопаснее, чем ответ Songyuanyao. Я буду помнить об этом, если окажусь в подобной ситуации, когда мне потребуется больше гибкости в обработке ошибок (в моей текущей ситуации просто нужно обработать исключение и создать новое с дополнительной информацией). - person Dave; 25.03.2017