#c #exception
#c #исключение
Вопрос:
Я хочу знать, изменяет ли запись исключений в поле входящие и исходящие поведение конкретной программы или нет, например, throw MyException(); и throw (MyException());
Мой код:
#include <iostream>
#include <exception>
using namespace std;
class MyException: public exception {
public:
virtual const char* what() const throw()
{
return "Something bad happened";
}
};
class Test
{
public:
void goWrong()
{
throw (MyException());
}
};
int main()
{
Test test;
try
{
test.goWrong();
}
catch (MyException amp;err)
{
cout << "The Exception is Executed: " << err.what() << 'n';
}
cout << "Still Running" << 'n';
return 0;
}
Комментарии:
1. Объект-исключение копируется / перемещается средой выполнения (может быть опущен) и после этого не имеет значения const. Можно было бы перехватить по ссылке для полиморфного поведения, но только сделать его const для защиты от мутации, и в этом нет необходимости.
2. @Bathsheba Хорошо, и я думаю, что return и throw одинаковы, но использование throw упрощает время отладки, чтобы определить, что это throw, и он должен возвращать исключение, если я не ошибаюсь.
3. Я полагаю, что () может заставить его выдавать ссылку на операторы возврата, заключенные в скобки. Это приведет к копированию ссылки на объект, что может быть проблемой, если исходное временное исключение, на которое оно указывает, будет уничтожено.
4. (Я думаю, что должна быть медаль для нового пользователя, чей первый вопрос достигает 5).
Ответ №1:
Объект исключения инициализируется копированием ( except.throw/3
), поэтому на самом деле не имеет значения, какой из них вы используете; результат тот же.
Инициализация копирования будет игнорировать ссылочный квалификатор, даже если вы получили его из этого.
Мы можем доказать это с помощью некоторого вывода трассировки:
#include <cstdio>
using std::printf;
struct T
{
T() { printf("T()n"); }
~T() { printf("~T()n"); }
T(const Tamp;) { printf("T(const Tamp;)n"); }
T(Tamp;amp;) { printf("T(Tamp;amp;)n"); }
Tamp; operator=(const Tamp;) { printf("Tamp; operator=(const Tamp;)n"); return *this; }
Tamp; operator=(const Tamp;amp;) { printf("Tamp; operator=(Tamp;amp;)n"); return *this; }
};
int main()
{
try
{
throw T();
}
catch (const Tamp;) {}
}
Даже если вы переключитесь с throw T()
на throw (T())
семантику (и вывод), они точно такие же:
T()
T(Tamp;amp;)
~T()
~T()
То есть создается временный T()
объект, затем перемещается в реальный объект исключения (который существует в каком-то волшебном «безопасном пространстве»), и в конечном итоге оба уничтожаются.
Обратите внимание, что, чтобы увидеть это доказательство, вам нужно вернуться к C 14 (поскольку C 17 не материализует это временное, пока не потребуется реальный объект исключения, согласно так называемому «обязательному исключению») и отключить необязательное исключение до C 17 (например -fno-elide-constructors
, в GCC).
Если бы, как утверждали некоторые другие, могла существовать такая вещь, как «выбрасывание ссылки» (которая стала висячей), вы бы видели только одну конструкцию T
. По правде говоря, не существует такой вещи, как выражения ссылочного типа, несмотря на все усилия decltype
и auto
притворяться, что они есть.
В обоих случаях выражение, которое мы даем throw
, является значением rvalue MyException
.
Причина, по которой мы должны быть осторожны return
при использовании выведенного возвращаемого типа (via decltype(auto)
), заключается в том, что вычет учитывает категорию значения. Если у вас есть локальная переменная int x
, то в return x
выражении x
есть значение xvalue int
, поэтому ваш выведенный тип будет int
и все в порядке. Если вы пишете return (x)
вместо этого, то выражение имеет значение lvalue int
, что приводит к выведенному типу intamp;
, и внезапно у вас возникают проблемы. Обратите внимание, что ни одно из выражений не имеет ссылочного типа (вещь, которая фактически не существует). Но в любом случае все это не имеет отношения к вашему throw
сценарию, не в последнюю очередь потому, что ваш аргумент является временным в первую очередь.
Комментарии:
1. Это означает, что я могу безопасно использовать все, что захочу, под / из?
2. @Ali-Baba Да, но я не знаю, почему вы хотите поместить объект исключения в круглые скобки; никто другой этого не делает.
3. «Инициализация копирования будет игнорировать ссылочный квалификатор, даже если вы получили его из этого». Но если вы пишете
int a; intamp; b = a;
, а затем копируете-инициализируете c подобнымintamp; c = b;
образом, то c копирует ссылку, но не объект, на который она указывает. Дело в том, что в throw по сути есть decltype, потому что компилятор определяет семантику копирования из заданного типа. Вы инициализируете копированиеMyExceptionamp;
, а не aMyException
.4. @Anonymous1847 Это неправда. Это выражение
b
имеет значение lvalueint
. Квалификаторы ссылок отбрасываются для всей обработки. (Вот почему вам придется писатьstd::move
снова, если вы хотите распространить ссылку на значение rvalue).c
привязан кa
нему точно так же, какb
есть; это точно такой же механизм.a
также не был скопирован! Не существует такой вещи, как «инициализация копирования aMyExceptionamp;
«, потому что ссылки являются иллюзией и на самом деле не существуют. Тип передаваемого выраженияthrow
в каждом случае равен rvalueMyException
. Вот и все.5. Ссылки — это иллюзия? Тогда почему вы не можете вернуть ссылку на локальную переменную? Да,
c
обязательноa
нравитсяb
.a
не копируется. Существует только один фактическийint
объект, который никогда не копируется, и в случае исключения этот отдельный объект будет уничтожен при развертывании стека.
Ответ №2:
Я полагаю, что это будет работать как оператор return в C 14 и более поздних версиях, в которых в круглых скобках указывается тип, который будет использоваться в качестве ссылки, а не сам экземпляр исключения. Ссылка будет сохранена, скопирована и обработана как исключение, но временный объект, на который указывает ссылка, вероятно, будет уничтожен при развертывании стека, что может вызвать проблемы. Это может не вызвать немедленных проблем, если ваш объект исключения имеет тип POD, поскольку уничтожение такого объекта, созданного в стеке, является тривиальным, но это все еще неопределенное поведение.
Комментарии:
1. Вы уверены? Объекты исключения обычно находятся в специальном зарезервированном месте. Я думаю, что это требует дополнительного анализа, прежде чем прийти к выводу.
2. @AsteroidsWithWings Ах, но фактический объект исключения — это ссылка, фактически указатель. Объект, на который он указывает, является временным.
3. «фактический объект исключения является ссылкой, фактически указателем». Нет, это не так. «Объект, на который он указывает, является временным». Нет, это не так.
4. Выбрасываемый тип — a
MyExceptionamp;
. Это исключение.MyExceptionamp;
s будут скопированы,MyException
не будут.5. Это просто неверно. Вы в корне не понимаете, как работают выражения в C .