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

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


person yuanlu0210    schedule 22.10.2017    source источник
comment
В первом случае объект разрезается.   -  person StoryTeller - Unslander Monica    schedule 22.10.2017
comment
@StoryTeller Спасибо! Каковы статический и динамический типы выражения b во втором случае? Base* и Derived* соответственно?   -  person yuanlu0210    schedule 22.10.2017
comment
Выражения не имеют динамического типа, только объекты.   -  person StoryTeller - Unslander Monica    schedule 22.10.2017
comment
Книга пытается упростить некоторые очень сложные принципы языка для новичков. Выражение может оценивать что-то, что называет объект. Статический тип объекта — это тип выражения (иногда скорректированный). Но только объект имеет динамический тип, формально.   -  person StoryTeller - Unslander Monica    schedule 22.10.2017
comment
@StoryTeller В заключение, каждое выражение имеет статический тип, но не имеет динамического типа, я на правильном пути? Если да, то каков статический тип выражения b? Это База*?   -  person yuanlu0210    schedule 22.10.2017
comment
Во втором случае? Да, это Base* (тип указателя).   -  person StoryTeller - Unslander Monica    schedule 22.10.2017
comment
Я откатил ваш добавить еще один вопрос. Пожалуйста, не делай этого. Это делает недействительными существующие ответы и рискует превратить этот пост в слишком широкий. Если у вас есть дополнительный вопрос, опубликуйте его как еще один вопрос. Вы можете вернуться к этому из нового, чтобы не повторять слишком много деталей.   -  person StoryTeller - Unslander Monica    schedule 22.10.2017


Ответы (3)


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

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

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

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


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

person StoryTeller - Unslander Monica    schedule 22.10.2017
comment
Спасибо!! Теперь я вижу болтающийся указатель. А первый случай? У меня была висящая ссылка или нет? - person yuanlu0210; 22.10.2017
comment
@ivy - Нет. В первом случае объект исключения представляет собой Base (в большинстве выражений тип T& настраивается на T). Таким образом, указанный объект был скопирован, и поэтому я сказал вам, что он был нарезан. - person StoryTeller - Unslander Monica; 22.10.2017
comment
Можно ли все это считать следствием того, что при использовании операции throw первым шагом будет копирование инициализации объекта временного исключения из выражения? То есть как если бы я сделал Base e = b; e.print(std::cout); в первом случае (здесь b — ссылка) и Base* e = b; e->print(std::cout); во втором случае (здесь b — указатель). Это правильный образ мыслей? (Все еще не могу обойти статическую и динамическую типизацию...). - person yuanlu0210; 22.10.2017
comment
@ivy - Вы можете думать об этом как о том, что происходит. Есть только один незначительный момент, и это то, как вы улавливаете. Если вы делаете это по значению, а не по ссылке, объект исключения снова копируется в предложение catch. - person StoryTeller - Unslander Monica; 22.10.2017

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

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

person user7860670    schedule 22.10.2017

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

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

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:

Во-первых, копирование-инициализация объекта исключения из выражения

Эта инициализация копирования нарезает объект, как если бы вы назначили Base c = b

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

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

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

person Daniel Trugman    schedule 22.10.2017