Может ли кто-нибудь объяснить ссылки на 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)


На самом деле обработка исключений имеет специальные правила в отношении lvalue и rvalue. Объект временного исключения представляет собой lvalue, см. 15.1/3 текущего черновика:

Выражение throw инициализирует временный объект, называемый объектом исключения, тип которого определяется путем удаления любых cv-квалификаторов верхнего уровня из статического типа операнда throw и настройки типа из «массива T» или « функция, возвращающая T» в «указатель на T» или «указатель на функцию, возвращающую T» соответственно. Временное значение представляет собой lvalue и используется для инициализации переменной, названной в соответствующем обработчике (15.3). Если тип объекта исключения будет неполным типом или указателем на неполный тип, отличный от void (возможно, cv-qualified), программа будет неправильно сформирована. За исключением этих ограничений и ограничений на сопоставление типов, упомянутых в 15.3, операнд throw обрабатывается точно так же, как аргумент функции в вызове (5.2.2) или операнд оператора return.

И захват по ссылке 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
}

В зависимости от категории значения аргумента дедукция аргумента шаблона выводит T либо как ссылку на lvalue, либо как нормальный тип значения. Из-за свертывания ссылок T&& также является ссылкой lvalue в первом случае или ссылкой rvalue во втором случае. Если вы видите, что T&& и T - это параметр шаблона, который можно вывести, это в основном "поймать все". std::forward восстанавливает исходную категорию значений (закодированную в T), поэтому мы можем идеально перенаправить аргумент в перегруженные внутренние функции и выбрать правильный. Но это работает только потому, что external является шаблоном и существуют специальные правила для определения 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, и вам. Но ИМХО, несмотря на то, что цитирование стандарта идеально и точно, весь смысл перехода к SO состоит в том, чтобы получить более подробные разъяснения или объяснения по стандарту, чтобы его было легко понять для n00b. Любой может прочитать стандарт, но понять его — совсем другое дело :( - person legends2k; 05.10.2010
comment
@ legends2k: я обновил ответ, чтобы пролить больше света на то, как работает идеальная переадресация. - person sellibitze; 05.10.2010
comment
@sellibitze: Да, это проясняет для меня. Теперь я понимаю, почему вы сказали, что move — это то, что я пытался сделать, а не идеальная переадресация, поскольку функция имеет только ссылку rvalue. Спасибо за объяснение :) - person legends2k; 05.10.2010