Статический тип объекта исключения

Я прочитал следующее из 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 не должны быть базовыми *?

3

Решение

Когда мы бросаем выражение, статический тип этого выражения во время компиляции определяет тип объекта исключения

Вышесказанное полностью верно. Что вы забыли, так это то, что указатели тоже являются объектами. И когда вы бросаете указатель, это ваш объект исключения.

Объект вы должен иметь динамично1 выделенный по-прежнему указывает на это Base* указатель. И никакая нарезка не происходит на этом, потому что нет никакой попытки скопировать это. Таким образом, динамический диспетчер через указатель обращается к Derived объект, и этот объект будет использовать переопределяющую функцию.

Именно из-за этого «несоответствия» обычно лучше создать объект исключения в самом выражении throw.



1 Этот указатель указывает на локальный объект, вы сделали большой «нет-нет» и получили висячий указатель.

2

Другие решения

В первом случае вы бросаете свежий экземпляр Base класс, вызывающий конструктор копирования, потому что вы передаете ссылку на Base в throw оператор.

Во втором случае вы бросаете указатель на выделенный стеком объект типа Derived когда выходит исключение, это выходит за рамки видимости, поэтому вы фиксируете и затем разыменовываете свисающий указатель, вызывающий неопределенное поведение.

2

Первый сценарий

Я думаю, что если вы добавите несколько отпечатков в ваши классы, вы сможете увидеть более четкую картину:

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

Второй сценарий

Во-первых, вы бросаете указатель на локальный объект, вызывая неопределенное поведение, пожалуйста, избегайте этого во что бы то ни стало!

Допустим, вы исправили это и бросили динамически размещенный указатель, он работает, поскольку вы бросаете указатель, который не влияет на объект и сохраняет информацию о динамическом типе.

0
По вопросам рекламы [email protected]