опитайте/уловете около референтната задача

Бих искал да хвана изключение от метод, който връща референция, без да хвана същото изключение от по-късно извикване, което използва референцията. Например:

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
@Дейв, без нужда от справка, би било малко по-просто: [&] { ... }(). Не бих нарекъл това многословно, като се има предвид, че останалото е код, който така или иначе пишете. Необходимостта от изричния тип връщане обаче е малко досадна и има малък шанс един ден да изглежда като [&]() => { ... }() вместо това, въпреки че бих се надявал повече, когато тялото е прост израз. - 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
Хубава идея, но предпочитам решението на Крис, тъй като запазва семантиката на C++ без нулеви стойности. Предполагам, че ред Something &useThisO = *o; точно след опита поне ще постави това на едно място, но все още съществува рискът catch блокът да се справи с грешката и да забрави да зададе o, което води до грешка по време на изпълнение. - person Dave; 25.03.2017

Мисля, че това е добър случай за използване на boost::optional (std:: в c++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
Това е добър начин за справяне, с предимството, че позволява повече типове обработка на грешки от отговора на Крис и е по-безопасен от отговора на Сонгюаняо. Ще го имам предвид, ако попадна в подобна ситуация, в която имам нужда от повече гъвкавост при обработката на грешки (текущата ми ситуация просто трябва да обработи изключението и да хвърли ново с повече информация). - person Dave; 25.03.2017