Может ли кто-нибудь объяснить rvalue ссылки в отношении исключений? - PullRequest
9 голосов
/ 04 октября 2010

Допустим, у меня есть этот класс исключений:

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, чтобы этот вид использования был действительно значимым?Я пытался найти статьи и ТАК вопросы по ссылкам на значения, связанные с исключениями, но не смог их найти.

1 Ответ

7 голосов
/ 05 октября 2010

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

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

И отлов по ссылке rvalue тоже незаконен, см. 15.3 / 1:

Объявление-исключение в обработчике описывает тип (ы) исключений, которые могут привести к вводу этого обработчика. Объявление-исключение не должно обозначать неполный тип или ссылочный тип rvalue . Объявление исключения не должно обозначать указатель или ссылку на неполный тип, кроме void *, const void *, volatile void * или const volatile void *.

Кроме того, вы, похоже, не понимаете идеальную пересылку. Ваш прямой вызов не лучше, чем ход. Идея идеальной пересылки состоит в том, чтобы закодировать категорию значения аргумента как часть типа и дать понять выводу аргумента шаблона. Но ваш обработчик исключений не является и не может быть шаблоном функции.

По сути, совершенная пересылка зависит от вывода аргументов шаблона и ссылок на значения:

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.

...