Може ли някой да обясни препратките към rvalue по отношение на изключенията?

Да кажем, че имам този клас изключения:

struct MyException : public std::exception
{
    MyException(const std::exception &exc) : std::exception(exc)
    {
        cout << "lval\n";
    }
    MyException(std::exception &&exc) : std::exception(std::forward<std::exception>(exc))
    {
        cout << "rval\n";
    }
};

...
...

try
{
    throw std::exception("Oh no!");
    // above is rvalue since it's got no name, what if the throw is made as
    // std::exception lvalExc("Oh wierd!");
    // throw lvalExc;
    // if the throw is made thus, how can it be caught by catch(std::exception &&exc)?
}
catch(std::exception &&rValRef)
{
    cout << "rValRef!\n";
    throw MyException(std::forward<std::exception>(rValRef));
}

Когато се опитах да хвана по стойност или по (const) lvalue ref. компилаторът казва, че тези случаи вече се обработват от клаузата rvalue ref catch, което е разбираемо, тъй като изключение е xvalue и може би най-добрият начин за улавяне на xvalue е rvalue ref (поправете ме, ако греша). Но може ли някой да обясни за перфектното препращане в горния случай на създаване на изключение? Правилно ли е? Въпреки че се компилира, има ли смисъл или полезно? Трябва ли C++ библиотеката, която използвам, да има внедрен конструктор за преместване за нейния std::exception, за да бъде този вид употреба наистина смислена? Опитах се да търся статии и SO въпроси за препратки към rvalue по отношение на изключения, не можах да намеря такива.


person legends2k    schedule 04.10.2010    source източник


Отговори (1)


Всъщност обработката на изключения има специални правила по отношение на lvalues ​​и rvalues. Обектът за временно изключение е lvalue, вижте 15.1/3 от текущата чернова:

Изразът за хвърляне инициализира временен обект, наречен обект за изключение, чийто тип се определя чрез премахване на всички cv-квалификатори от най-високо ниво от статичния тип на операнда на хвърляне и коригиране на типа от „масив от T“ или „ функция, връщаща T” съответно в „указател към T” или „указател към функция, връщаща T”. Временното е lvalue и се използва за инициализиране на променливата, посочена в съвпадащия манипулатор (15.3). Ако типът на обекта за изключение би бил непълен тип или указател към непълен тип, различен от (възможно cv-квалифициран) void, програмата е неправилно оформена. С изключение на тези ограничения и ограниченията за съвпадение на типове, споменати в 15.3, операндът на throw се третира точно като функционален аргумент в извикване (5.2.2) или като операнд на израз за връщане.

И прихващането чрез препратка към rvalue също е незаконно, вижте 15.3/1:

Декларацията за изключение в манипулатор описва типа(овете) изключения, които могат да причинят въвеждането на този манипулатор. Декларацията за изключение не трябва да обозначава непълен тип или референтен тип rvalue. Декларацията за изключение не трябва да обозначава указател или препратка към непълен тип, различен от void*, const void*, volatile void* или const volatile void*.

Освен това изглежда не разбирате перфектното препращане. Вашето извикване напред не е по-добро от движение. Идеята на перфектното препращане е да се кодира стойностната категория на аргумента като част от типа и да се остави дедукцията на аргумент на шаблона да я разбере. Но вашият манипулатор на изключения не е и не може да бъде шаблон на функция.

По принцип перфектното препращане разчита на дедукция на аргументи на шаблон и препратки към rvalue:

void inner(const int&);  // #1 takes only lvalues or const rvalues
void inner(int&&);       // #2 takes non-const rvalues only

template<class T>
void outer(T && x) {
    inner(forward<T>(x));
}

int main() {
   int k = 23;
   outer(k);   // outer<T=int&> --> forward<int&> --> #1
   outer(k+2); // outer<T=int>  --> forward<int>  --> #2
}

В зависимост от категорията на стойността на аргумента, дедукцията на шаблон argumend извежда T като референция на lvalue или нормален тип стойност. Поради свиването на препратката, T&& също е препратка към lvalue в първия случай или препратка към rvalue във втория случай. Ако видите T&& и T е параметър на шаблон, който може да бъде изведен, това е основно „улавяне на всичко“. std::forward възстановява оригиналната стойностна категория (кодирана в T), така че можем перфектно да препратим аргумента към претоварените вътрешни функции и да изберем правилната. Но това работи само защото outer е шаблон и защото има специални правила за определяне на T по отношение на неговата стойностна категория. Ако използвате препратки към rvalue без дедукция на шаблони/шаблонни аргументи (както в #2), функцията ще приема само rvalue.

person sellibitze    schedule 05.10.2010
comment
+1: Вие казвате това, което аз казах, но повече и по-добре. Също така, само за да направим съвета ясен, обикновена неконстантна препратка ще съвпадне и ще може да модифицира обекта за изключение. - person Potatoswatter; 05.10.2010
comment
@Potatoswatter: Съжалявам, че повтарям половината от отговора ви. Всъщност пропуснах препратката ви към 15.1/3, когато прегледах отговора ви. - person sellibitze; 05.10.2010
comment
@sellibitze: Няма смисъл да ги правим взаимно изключващи се. Вие сте свободни да заемате всичко, стига отговорът ви да е правилен :v) . - person Potatoswatter; 05.10.2010
comment
@sellibitze: За клас MyException ли говорите? Ако отговорът е да, той няма шаблон за функция. Също така бихте ли могли да обясните какво е перфектното пренасочване е да се кодира стойностната категория на аргумента като част от типа и да се остави дедукцията на аргумент на шаблон да го разбере. - person legends2k; 05.10.2010
comment
@sellibitze: Благодаря за отговора момчета - както Potatoswatter, така и вие. Но IMHO, въпреки че цитирането на стандарта е перфектно и точно, целият смисъл на идването на SO е да получите по-добри разяснения или обяснения на стандарта по-нататък, така че да е лесен за разбиране за n00b. Всеки може да чете стандарта, но разбирането му ясно е съвсем различна история :( - person legends2k; 05.10.2010
comment
@legends2k: Актуализирах отговора, за да хвърля малко повече светлина върху това как работи перфектното пренасочване. - person sellibitze; 05.10.2010
comment
@sellibitze: Да, това ми изяснява. Сега разбирам защо казахте, че преместване е това, което се опитвах да направя, а не перфектно пренасочване, тъй като функцията има само препратка към rvalue. Благодаря за обяснението :) - person legends2k; 05.10.2010