C ++: бросание производного класса по ссылке не работает при перехвате базового класса - PullRequest
11 голосов
/ 23 марта 2012

Я хочу выбросить свои собственные исключения с базовым классом Exception.Существует виртуальный метод print, который будет перезаписан подклассами.Я только ловлю тип Exception& и использую print, чтобы получить конкретную ошибку.Проблема в том, что после того, как я выбрасываю ссылку на подкласс, она обрабатывается так, как если бы это был базовый класс.

Вот пример:

#include <iostream>
using namespace std;

class Exception
{
    public:
        virtual void print()
        {
            cout << "Exception" << endl;
        }
};

class IllegalArgumentException : public Exception
{
    public:
        virtual void print()
        {
            cout << "IllegalArgumentException" << endl;
        }
};

int main(int argc, char **argv)
{
    try
    {
        IllegalArgumentException i;
        Exception& ref = i;

        cout << "ref.print: ";
        ref.print();

        throw ref;
    }
    catch(Exception& e)
    {
        cout << "catched: ";
        e.print();
    }
}

Вывод этого примера:

ref.print: IllegalArgumentException
catched: Exception

Использование ссылки должно привести к использованию метода print из производного класса.Внутри блока try ссылка действительно его использует.Почему перехваченный Exception& не действует как IllegalArgumentException и как я могу получить такое поведение?

Кажется, что следующий код делает то, что он должен:

try
{
    IllegalArgumentException i;
    Exception* pointer = &i;

    throw pointer;
}
catch(Exception* e)
{
    cout << "pointer catched: ";
    e->print();
}

но не становится ли указатель недопустимым за пределами блока try?Тогда было бы рискованно делать это, и если я выделю память в куче, чтобы обойти эту проблему, я несу ответственность за удаление внутри блока catch, что тоже не красиво.Так как бы вы решили эту проблему?

Ответы [ 4 ]

6 голосов
/ 23 марта 2012

throw неявно копирует, и, следовательно, фрагменты. Цитата C ++ 11, §15.1 / 3:

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

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

1 голос
/ 23 марта 2012

То, что вы видите, называется нарезкой.

Вы, очевидно, привыкли к полиморфизму, когда вы можете назначить указатель на подкласс для указателя на базовый класс, и вы сохраняете (под) тип и все данные. В конце концов, это был просто копируемый указатель, а не сам объект. Однако ничего подобного не может произойти, когда присваивание находится непосредственно между самими объектами; базовый класс обычно имеет более короткие экземпляры, и может не быть доступного места (в вашем случае, в стеке; в другом случае - в куче) после переменной типа базового класса.

Итак, C ++ определен для выполнения нарезки на объекте. Копируется только часть базового класса, а тип также "понижается" до базового класса.

0 голосов
/ 07 сентября 2013

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

int main(int argc, char **argv)
{
    try
    {
        IllegalArgumentException* pIAE = new IllegalArgumentException();
        throw pIAE;
    }
    catch(IllegalArgumentException* i)
        {
            i->print();
    }
}

Захваченный указатель указывает на тот же объект, потому что конструктор копирования не вызывается неявно.

0 голосов
/ 23 марта 2012

Когда вы бросаете объект, копия этого объекта создается в каком-то другом месте, поэтому раскрутка стека может продолжаться, и копия может быть передана в обработчик исключений (либо по ссылке, поэтому дальнейшие копии не создаются, либо по значению, создающему второй экземпляр).

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

Когда реализация копирует объект, который вы бросаете, она смотрит на статический тип выражения, а не на динамический тип. Это означает, что в вашем коде он видит, что вы выбрасываете Exception, и поэтому полученная копия является примером нарезки (т.е. вместо копирования всего объекта, только подобъекта базового класса копируется).

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

Это должно вывести «catch: IllegalArgumentException».

try
{
    IllegalArgumentException i;

    throw i;
}
catch(Exception& e)
{
    cout << "caught: ";
    e.print();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...