Я прочитал следующее из C ++ Primer (5-е издание, раздел 18.1.1):
«Когда мы бросаем выражение, статический тип этого выражения во время компиляции определяет тип объекта исключения». Поэтому я попробовал следующий код:
#include <iostream>
class Base{
public:
virtual void print(std::ostream& os){os << "Base\n";}
};
class Derived: public Base{
public:
void print(std::ostream& os){os << "Derived\n";}
};
int main(){
try{
Derived d;
Base &b = d;
b.print(std::cout); //line 1
throw b;
}
catch(Base& c){
c.print(std::cout); //line 2
}
return 0;
}
что дает мне следующий вывод:
Derived
Base
Я думаю, что понимаю, почему ожидается такой вывод: в строке 1 мы имеем динамическое связывание. Теперь, когда мы бросаем b, он основан на статическом типе b, что означает, что и статический тип, и динамический тип c являются базовыми.&и, следовательно, мы видим результат в строке 2.
Однако, если бы я использовал указатель вместо ссылки:
int main(){
try{
Derived d;
Base *b = &d;
b->print(std::cout); //line 1
throw b;
}
catch(Base* c){
c->print(std::cout); //line 2
}
return 0;
}
вывод теперь становится:
Derived
Derived
что, по-видимому, означает, что статический тип c — это Base *, а динамический тип c — это Derived *, почему? Разве статические и динамические типы c не должны быть базовыми *?
Когда мы бросаем выражение, статический тип этого выражения во время компиляции определяет тип объекта исключения
Вышесказанное полностью верно. Что вы забыли, так это то, что указатели тоже являются объектами. И когда вы бросаете указатель, это ваш объект исключения.
Объект вы должен иметь динамично1 выделенный по-прежнему указывает на это Base*
указатель. И никакая нарезка не происходит на этом, потому что нет никакой попытки скопировать это. Таким образом, динамический диспетчер через указатель обращается к Derived
объект, и этот объект будет использовать переопределяющую функцию.
Именно из-за этого «несоответствия» обычно лучше создать объект исключения в самом выражении throw.
1 Этот указатель указывает на локальный объект, вы сделали большой «нет-нет» и получили висячий указатель.
В первом случае вы бросаете свежий экземпляр Base
класс, вызывающий конструктор копирования, потому что вы передаете ссылку на Base
в throw
оператор.
Во втором случае вы бросаете указатель на выделенный стеком объект типа Derived
когда выходит исключение, это выходит за рамки видимости, поэтому вы фиксируете и затем разыменовываете свисающий указатель, вызывающий неопределенное поведение.
Первый сценарий
Я думаю, что если вы добавите несколько отпечатков в ваши классы, вы сможете увидеть более четкую картину:
struct Base {
Base() { std::cout << "Base c'tor\n"; }
Base(const Base &) { std::cout << "Base copy c'tor\n"; }
virtual void print(std::ostream& os) { std::cout << "Base print\n"; }
};
struct Derived: public Base {
Derived() { std::cout << "Derived c'tor\n"; }
Derived(const Derived &) { std::cout << "Derived copy c'tor\n"; }
virtual void print(std::ostream& os) { std::cout << "Derived print\n"; }
};
И вывод:
Base c'tor
Derived c'tor
Derived print
throwing // Printed right before `throw b;` in main()
Base copy c'tor
Base print
Как видите, при звонке throw b;
есть копия конструкции другого временного Base
объект для исключения. От cppreference.com:
Сначала copy-инициализирует объект исключения из выражения
Это копия-инициализация ломтики объект, как будто вы назначены Base c = b
Второй сценарий
Во-первых, вы бросаете указатель на локальный объект, вызывая неопределенное поведение, пожалуйста, избегайте этого во что бы то ни стало!
Допустим, вы исправили это и бросили динамически размещенный указатель, он работает, поскольку вы бросаете указатель, который не влияет на объект и сохраняет информацию о динамическом типе.