Каким образом реализуют наследование классов ошибок

I’ve just created exception hierarchy and wanted to pass char* to constructor of one of my derived classes with a message telling what’s wrong, but apparently std::exception doesn’t have constructor which would allow me to do so. Yet there is a class member called what() which would suggest that some information can be passed.
How can I (can I?) pass text to derived class of a std::exception in order to pass info with my exception class, so I can say somewhere in the code:

throw My_Exception("Something bad happened.");

kmiklas's user avatar

kmiklas

13k21 gold badges66 silver badges102 bronze badges

asked Nov 16, 2011 at 13:51

smallB's user avatar

3

If you want to make use of the string constructor, you should inherit from std::runtime_error or std::logic_error which implements a string constructor and implements the std::exception::what method.

Then it’s just a case of calling the runtime_error/logic_error constructor from your new inherited class, or if you’re using c++11 you can use constructor inheritance.

Frank Kusters's user avatar

answered Nov 16, 2011 at 13:53

obmarg's user avatar

obmargobmarg

9,31935 silver badges58 bronze badges

1

I use the following class for my exceptions and it works fine:

class Exception: public std::exception
{
public:
    /** Constructor (C strings).
     *  @param message C-style string error message.
     *                 The string contents are copied upon construction.
     *                 Hence, responsibility for deleting the char* lies
     *                 with the caller. 
     */
    explicit Exception(const char* message)
        : msg_(message) {}

    /** Constructor (C++ STL strings).
     *  @param message The error message.
     */
    explicit Exception(const std::string& message)
        : msg_(message) {}

    /** Destructor.
     * Virtual to allow for subclassing.
     */
    virtual ~Exception() noexcept {}

    /** Returns a pointer to the (constant) error description.
     *  @return A pointer to a const char*. The underlying memory
     *          is in posession of the Exception object. Callers must
     *          not attempt to free the memory.
     */
    virtual const char* what() const noexcept {
       return msg_.c_str();
    }

protected:
    /** Error message.
     */
    std::string msg_;
};

OverShifted's user avatar

answered Nov 16, 2011 at 14:03

tune2fs's user avatar

tune2fstune2fs

7,5875 gold badges41 silver badges57 bronze badges

8

How about this:

class My_Exception : public std::exception
{
public:
virtual char const * what() const { return "Something bad happend."; }
};

Or, create a constructor accepting the description if you like…

answered Nov 16, 2011 at 13:54

3

If your goal is to create an exception so that you do not throw a generic exception (cpp:S112) you may just want to expose the exception you inherit from (C++11) with a using declaration.

Here is a minimal example for that:

#include <exception>
#include <iostream>

struct myException : std::exception
{
    using std::exception::exception;
};

int main(int, char*[])
{
    try
    {
        throw myException{ "Something Happened" };
    }
    catch (myException &e)
    {
        std::cout << e.what() << std::endl;
    }
    return{ 0 };
}

As Kilian points out in the comment section the example depends on a specific implementation of std::exception that offers more constructors than are mentioned here.

In order to avoid that you can use any of the convenience classes predefined in the header <stdexcept>. See these «Exception categories» for inspiration.

answered Jul 12, 2018 at 20:18

Johannes's user avatar

JohannesJohannes

6,4308 gold badges58 silver badges107 bronze badges

2

The what method is virtual, and the meaning is that you should override it to return whatever message you want to return.

answered Nov 16, 2011 at 13:54

Some programmer dude's user avatar

0

Here is an example

 class CommunicationError: public std::exception {
  public:
   explicit CommunicationError(const char* message) : msg(message) {}
   CommunicationError(CommunicationError const&) noexcept = default;

   CommunicationError& operator=(CommunicationError const&) noexcept = default;
  ~CommunicationError() override = default;

  const char* what() const noexcept override { return msg; }
 private:
  const char* msg;
};

[1] https://www.autosar.org/fileadmin/user_upload/standards/adaptive/17-03/AUTOSAR_RS_CPP14Guidelines.pdf

answered Oct 6, 2021 at 8:28

rjhcnf's user avatar

rjhcnfrjhcnf

7746 silver badges9 bronze badges

Добавлено 11 сентября 2021 в 12:17

Исключения и функции-члены

До этого момента в данном руководстве вы видели исключения, используемые только в функциях, не являющихся членами. Однако исключения также полезны в функциях-членах и тем более в перегруженных операторах. Рассмотрим следующий перегруженный оператор [] как часть простого класса целочисленного массива:

int& IntArray::operator[](const int index)
{
    return m_data[index];
}

Хотя эта функция будет отлично работать, пока index является допустимым индексом массива, ей очень не хватает проверки на ошибку. Мы могли бы добавить инструкцию assert, чтобы убедиться, что index корректен:

int& IntArray::operator[](const int index)
{
    assert (index >= 0 && index < getLength());
    return m_data[index];
}

Теперь, если пользователь передаст недопустимый индекс, программа вызовет ошибку утверждения. К сожалению, поскольку перегруженные операторы предъявляют особые требования к количеству и типу параметров, которые они могут принимать и возвращать, у них нет гибкости для передачи кодов ошибок или логических значений в вызывающую функцию для обработки. Однако, поскольку исключения не изменяют сигнатуру функции, они могут быть здесь очень полезны. Например:

int& IntArray::operator[](const int index)
{
    if (index < 0 || index >= getLength())
        throw index;

    return m_data[index];
}

Теперь, если пользователь передает недопустимый индекс, operator[] вызовет исключение типа int.

Когда конструкторы дают сбой

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

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

Это приводит к вопросу о том, что мы должны делать, если мы выделили ресурсы в нашем конструкторе, а затем возникает исключение до завершения конструктора. Как обеспечить правильное освобождение уже выделенных ресурсов? Один из способов – обернуть любой код, который может дать сбой в блок try, использовать соответствующий блок catch для перехвата исключения и выполнить любую необходимую очистку, а затем повторно выбросить исключение (эту тему мы обсудим в уроке «20.6 – Повторное выбрасывание исключений»). Однако это добавляет много бардака, и здесь легко ошибиться, особенно если ваш класс выделяет несколько ресурсов.

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

Например:

#include <iostream>

class Member
{
public:
    Member()
    {
        std::cerr << "Member allocated some resourcesn";
    }

    ~Member()
    {
        std::cerr << "Member cleaned upn";
    }
};

class A
{
private:
    int m_x {};
    Member m_member;

public:
    A(int x) : m_x{x}
    {
        if (x <= 0)
            throw 1;
    }

    ~A()
    {
        std::cerr << "~An"; // не должен вызываться
    }
};


int main()
{
    try
    {
        A a{0};
    }
    catch (int)
    {
        std::cerr << "Oopsn";
    }

    return 0;
}

Этот код печатает:

Member allocated some resources
Member cleaned up
Oops

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

Это одна из причин того, что RAII (метод, описанный в уроке «12.9 – Деструкторы») так широко пропагандируется – даже в исключительных обстоятельствах классы, реализующие RAII, могут выполнять после себя очистку.

Однако создание пользовательского класса, такого как Member, для управления размещением ресурсов неэффективно. К счастью, стандартная библиотека C++ поставляется с RAII-совместимыми классами для управления распространенными типами ресурсов, такими как файлы (std::fstream, рассмотренные в уроке «23.6 – Основы файлового ввода/вывода») и динамическая память (std::unique_ptr и другие умные указатели, описанные в «M.1 – Введение в умные указатели и семантику перемещения»).

Например, вместо этого:

class Foo
private:
    int *ptr; // Foo будет обрабатывать выделение/освобождение

Сделайте так:

class Foo
private:
    std::unique_ptr<int> ptr; // std::unique_ptr будет обрабатывать выделение/освобождение

В первом случае, если конструктор Foo завершится со сбоем после того, как ptr выделил свою динамическую память, Foo будет отвечать за очистку, что может быть сложной задачей. Во втором случае, если конструктор Foo завершится со сбоем после того, как ptr выделил свою динамическую память, деструктор ptr выполнит и вернет эту память в систему. Foo не должен выполнять какую-либо явную очистку, когда обработка ресурсов делегируется членам, совместимым с RAII!

Классы исключений

Одна из основных проблем с использованием базовых типов данных (таких как int) в качестве типов исключений заключается в том, что они по своей сути непонятны. Еще бо́льшая проблема – это разрешение неоднозначности того, что означает исключение, когда в блоке try есть несколько инструкций или вызовов функций.

// Использование перегруженного operator[] класса IntArray
// из примера выше

try
{
    int* value{ new int{ array[index1] + array[index2]} };
}
catch (int value)
{
    // Что мы здесь ловим?
}

В этом примере, если бы мы перехватили исключение типа int, о чем это нам сказало бы? Был ли один из индексов массива вне допустимого диапазона? operator+ вызвал целочисленное переполнение? Сбой оператора new из-за нехватки памяти? К сожалению, в этом случае нет простого способа устранить неоднозначность. Хотя мы можем генерировать исключения const char* для решения проблемы определения, ЧТО пошло не так, это всё же не дает нам возможности обрабатывать исключения из разных источников по-разному.

Один из способов решить эту проблему – использовать классы исключений. Класс исключения – это просто обычный класс, специально созданный для выдачи исключения. Давайте спроектируем простой класс исключения, который будет использоваться с нашим классом IntArray:


#include <string>
#include <string_view>

class ArrayException
{
private:
    std::string m_error;

public:
    ArrayException(std::string error)
        : m_error{ error }
    {
    }

    std::string_view getError() const { return m_error; }
// В C++14 или более ранней версии, используйте вместо этого следующее
//    const char* getError() const { return m_error.c_str(); } 
};

Вот полный код программы, использующей этот класс:

#include <iostream>
#include <string>
#include <string_view>

class ArrayException
{
private:
    std::string m_error;

public:
    ArrayException(std::string error)
        : m_error{ error }
    {
    }

    std::string_view getError() const { return m_error; }
// В C++14 или более ранней версии, используйте вместо этого следующее
//    const char* getError() const { return m_error.c_str(); } 
};

class IntArray
{
private:

    int m_data[3]{}; // для простоты предполагаем, что массив имеет длину 3
public:
    IntArray() {}

    int getLength() const { return 3; }

    int& operator[](const int index)
    {
        if (index < 0 || index >= getLength())
            throw ArrayException{ "Invalid index" };

        return m_data[index];
    }

};

int main()
{
    IntArray array;

    try
    {
        int value{ array[5] }; // индекс вне допустимого диапазона
    }
    catch (const ArrayException& exception)
    {
        std::cerr << "An array exception occurred (" << exception.getError() << ")n";
    }
}

Используя такой класс, мы можем сделать так, чтобы исключение вернуло описание возникшей проблемы, которое предоставляет контекст для того, что пошло не так. А поскольку ArrayException – это собственный уникальный тип, мы можем специально перехватывать исключения, создаваемые классом массива, и при желании обрабатывать их иначе, чем другие исключения.

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

Исключения и наследование

Поскольку можно выбрасывать объекты классов, в качестве исключений, а классы могут быть производными от других классов, нам необходимо учитывать, что происходит, когда в качестве исключений мы используем наследованные классы. Оказывается, обработчики исключений будут не только соответствовать классам определенного типа, они также будут соответствовать классам, производным от этого конкретного типа! Рассмотрим следующий пример:

class Base
{
public:
    Base() {}
};

class Derived: public Base
{
public:
    Derived() {}
};

int main()
{
    try
    {
        throw Derived();
    }
    catch (const Base& base)
    {
        std::cerr << "caught Base";
    }
    catch (const Derived& derived)
    {
        std::cerr << "caught Derived";
    }

    return 0;
}

В приведенном выше примере мы генерируем исключение типа Derived. Однако результат этой программы:

caught Base

Что случилось?

Во-первых, как упоминалось выше, производные классы будут перехвачены обработчиками базового типа. Поскольку Derived является производным от Base, Derived «является» Base (между ними есть связь «является чем-либо»). Во-вторых, когда C++ пытается найти обработчик возникшего исключения, он делает это последовательно. Следовательно, первое, что делает C++, – это проверяет, соответствует ли обработчик исключений для Base исключению Derived. Поскольку Derived «является» Base, ответ – да, и он выполняет блок catch для типа Base! Блок catch для Derived в этом случае даже не проверяется.

Чтобы этот пример работал, как задумывалось, нам нужно изменить порядок блоков catch:

class Base
{
public:
    Base() {}
};

class Derived: public Base
{
public:
    Derived() {}
};

int main()
{
    try
    {
        throw Derived();
    }
    catch (const Derived& derived)
    {
        std::cerr << "caught Derived";
    }
    catch (const Base& base)
    {
        std::cerr << "caught Base";
    }

    return 0;
}

Таким образом, обработчик Derived получит первым шанс перехватить объекты типа Derived (до того, как это сделает обработчик для Base). Объекты типа Base не будут соответствовать обработчику Derived (Derived «является» Base, но Base не является Derived) и, таким образом, «провалятся вниз» до обработчика Base.

Правило


Обработчики для исключений производных классов должны быть идти перед обработчиками для базовых классов.

Возможность использовать обработчик для перехвата исключений производных типов с помощью обработчика для базового класса может быть чрезвычайно полезной.

std::exception

Многие классы и операторы в стандартной библиотеке в случае сбоя выдают исключения с объектами классов. Например, оператор new может выбросить std::bad_alloc, если не может выделить достаточно памяти. Неудачный dynamic_cast вызовет std::bad_cast. И так далее. Начиная с C++20, существует 28 различных классов исключений, которые могут быть сгенерированы, и в каждом последующем стандарте языка добавляется еще больше.

Хорошая новость заключается в том, что все эти классы исключений являются производными от одного класса std::exception. std::exception – это небольшой интерфейсный класс, предназначенный для использования в качестве базового класса для любого исключения, создаваемого стандартной библиотекой C++.

В большинстве случаев, когда стандартная библиотека генерирует исключение, нас не волнует, неудачное ли это выделение памяти, неправильное приведение или что-то еще. Нас просто волнует, что что-то катастрофически пошло не так, и теперь наша программа дает сбой. Благодаря std::exception мы можем настроить обработчик исключений для перехвата исключений типа std::exception, и в итоге мы перехватим и std::exception, и все производные исключения в одном месте. Всё просто!

#include <cstddef>   // для std::size_t
#include <iostream>
#include <exception> // для std::exception
#include <limits>
#include <string>    // для this example

int main()
{
    try
    {
        // Здесь идет ваш код, использующий стандартную библиотеку.
        // Для примера мы намеренно вызываем одно из ее исключений.
        std::string s;
        // вызовет исключение std::length_error или исключение выделения памяти
        s.resize(std::numeric_limits<std::size_t>::max());
    }
    // Этот обработчик перехватит std::exception и все производные от него исключения
    catch (const std::exception& exception)
    {
        std::cerr << "Standard exception: " << exception.what() << 'n';
    }

    return 0;
}

Приведенная выше программа печатает:

Standard exception: string too long

Приведенный выше пример довольно простой. В нем стоит отметить одну вещь: std::exception имеет виртуальную функцию-член what(), которая возвращает строку в стиле C с описанием исключения. Большинство производных классов переопределяют функцию what() для изменения этого сообщения. Обратите внимание, что эта строка предназначена для использования только для описательного текста – не используйте ее для сравнений, поскольку не гарантируется, что она будет одинаковой для разных компиляторов.

Иногда нам нужно обрабатывать определенный тип исключения по-другому. В этом случае мы можем добавить обработчик для этого конкретного типа и позволить всем остальным «проваливаться вниз» до обработчика базового типа. Рассмотрим следующий код:

try
{
     // здесь идет код, использующий стандартную библиотеку
}
// Этот обработчик здесь перехватит std::length_error
// (и любые производные от него исключения)
catch (const std::length_error& exception)
{
    std::cerr << "You ran out of memory!" << 'n';
}
// Этот обработчик перехватит std::exception (и любое исключение,
// производное от него), которое "провалится" сюда
catch (const std::exception& exception)
{
    std::cerr << "Standard exception: " << exception.what() << 'n';
}

В этом примере исключения типа std::length_error будут перехвачены и обработаны первым обработчиком. Исключения типа std::exception и всех других производных классов будут перехвачены вторым обработчиком.

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

Использование стандартных исключений напрямую

Ничто напрямую не вызывает std::exception, и вы тоже. Однако вы можете свободно использовать другие стандартные классы исключений из стандартной библиотеки, если они адекватно соответствуют вашим потребностям. Список всех стандартных исключений вы можете найти на cppreference.

std::runtime_error (включен как часть заголовка stdexcept) выбирается часто потому, что он имеет обобщенное название, а его конструктор принимает настраиваемое сообщение:

#include <iostream>
#include <stdexcept> // для std::runtime_error

int main()
{
    try
    {
        throw std::runtime_error("Bad things happened");
    }
    // Этот обработчик перехватит std::exception
    // и все производные от него исключения
    catch (const std::exception& exception)
    {
        std::cerr << "Standard exception: " << exception.what() << 'n';
    }

    return 0;
}

Этот код печатает:

Standard exception: Bad things happened

Создание собственных классов, производных от std::exception или std::runtime_error

Конечно, вы можете наследовать свои классы от std::exception и переопределять виртуальную константную функцию-член what(). Вот та же программа, что и выше, но с исключением ArrayException, производным от std::exception:

#include <exception> // для std::exception
#include <iostream>
#include <string>
#include <string_view>

class ArrayException : public std::exception
{
private:
    std::string m_error{}; // обрабатываем нашу строку

public:
    ArrayException(std::string_view error)
        : m_error{error}
    {
    }

    // std::exception::what() возвращает const char*,
    // поэтому мы должны делать так же, как она
    const char* what() const noexcept override { return m_error.c_str(); }
};

class IntArray
{
private:

    int m_data[3] {}; // для простоты предполагаем, что массив имеет длину 3
public:
    IntArray() {}

    int getLength() const { return 3; }

    int& operator[](const int index)
    {
        if (index < 0 || index >= getLength())
            throw ArrayException("Invalid index");

        return m_data[index];
    }

};

int main()
{
    IntArray array;

    try
    {
        int value{ array[5] };
    }
    catch (const ArrayException& exception) // блоки catch с производными классами идут первыми
    {
        std::cerr << "An array exception occurred (" << exception.what() << ")n";
    }
    catch (const std::exception& exception)
    {
        std::cerr << "Some other std::exception occurred (" << exception.what() << ")n";
    }
}

Обратите внимание, что виртуальная функция what() имеет спецификатор noexcept (что означает, что эта функция обещает не генерировать исключения). Следовательно, у нашего переопределения также должен быть спецификатор noexcept.

Поскольку std::runtime_error уже имеет возможности обработки строк, он также популярен в качестве базового класса для производных классов исключений. Вот тот же пример, использующий std::runtime_error:

#include <stdexcept> // для std::runtime_error
#include <iostream>
#include <string>

class ArrayException : public std::runtime_error
{
private:

public:
    // std::runtime_error принимает строку const char* с завершающим нулем.
    // std::string_view не может оканчиваться нулем, поэтому здесь это не лучший выбор.
    // Наше исключение ArrayException примет вместо него const std::string&,
    // которая гарантированно оканчивается нулем и может быть преобразована
    // в const char*.
    ArrayException(const std::string &error)
        : std::runtime_error{ error.c_str() } // std::runtime_error обработает эту строку
    {
    }

        // не нужно переопределять what(),
        // так как мы можем просто использовать std::runtime_error::what()
};

class IntArray
{
private:

    int m_data[3]{}; // для простоты предполагаем, что массив имеет длину 3
public:
    IntArray() {}

    int getLength() const { return 3; }

    int& operator[](const int index)
    {
        if (index < 0 || index >= getLength())
            throw ArrayException("Invalid index");

        return m_data[index];
    }

};

int main()
{
    IntArray array;

    try
    {
        int value{ array[5] };
    }
    catch (const ArrayException& exception) // блоки catch с производными классами идут первыми
    {
        std::cerr << "An array exception occurred (" << exception.what() << ")n";
    }
    catch (const std::exception& exception)
    {
        std::cerr << "Some other std::exception occurred (" << exception.what() << ")n";
    }
}

Вам решать, хотите ли вы создать свои собственные автономные классы исключений, использовать стандартные классы исключений или наследовать свои классы исключений от std::exception или std::runtime_error. Все эти подходы допустимы и зависят от ваших целей.

Теги

C++ / CppException / ИсключениеLearnCppRAII / Resource Acquisition Is Initialization / Получение ресурса есть инициализацияstd::exceptionstd::runtime_errorSTL / Standard Template Library / Стандартная библиотека шаблоновДля начинающихКласс (программирование)НаследованиеОбработка ошибокОбучениеПрограммирование

I’ve just created exception hierarchy and wanted to pass char* to constructor of one of my derived classes with a message telling what’s wrong, but apparently std::exception doesn’t have constructor which would allow me to do so. Yet there is a class member called what() which would suggest that some information can be passed.
How can I (can I?) pass text to derived class of a std::exception in order to pass info with my exception class, so I can say somewhere in the code:

throw My_Exception("Something bad happened.");

kmiklas's user avatar

kmiklas

13k21 gold badges66 silver badges102 bronze badges

asked Nov 16, 2011 at 13:51

smallB's user avatar

3

If you want to make use of the string constructor, you should inherit from std::runtime_error or std::logic_error which implements a string constructor and implements the std::exception::what method.

Then it’s just a case of calling the runtime_error/logic_error constructor from your new inherited class, or if you’re using c++11 you can use constructor inheritance.

Frank Kusters's user avatar

answered Nov 16, 2011 at 13:53

obmarg's user avatar

obmargobmarg

9,31935 silver badges58 bronze badges

1

I use the following class for my exceptions and it works fine:

class Exception: public std::exception
{
public:
    /** Constructor (C strings).
     *  @param message C-style string error message.
     *                 The string contents are copied upon construction.
     *                 Hence, responsibility for deleting the char* lies
     *                 with the caller. 
     */
    explicit Exception(const char* message)
        : msg_(message) {}

    /** Constructor (C++ STL strings).
     *  @param message The error message.
     */
    explicit Exception(const std::string& message)
        : msg_(message) {}

    /** Destructor.
     * Virtual to allow for subclassing.
     */
    virtual ~Exception() noexcept {}

    /** Returns a pointer to the (constant) error description.
     *  @return A pointer to a const char*. The underlying memory
     *          is in posession of the Exception object. Callers must
     *          not attempt to free the memory.
     */
    virtual const char* what() const noexcept {
       return msg_.c_str();
    }

protected:
    /** Error message.
     */
    std::string msg_;
};

OverShifted's user avatar

answered Nov 16, 2011 at 14:03

tune2fs's user avatar

tune2fstune2fs

7,5875 gold badges41 silver badges57 bronze badges

8

How about this:

class My_Exception : public std::exception
{
public:
virtual char const * what() const { return "Something bad happend."; }
};

Or, create a constructor accepting the description if you like…

answered Nov 16, 2011 at 13:54

3

If your goal is to create an exception so that you do not throw a generic exception (cpp:S112) you may just want to expose the exception you inherit from (C++11) with a using declaration.

Here is a minimal example for that:

#include <exception>
#include <iostream>

struct myException : std::exception
{
    using std::exception::exception;
};

int main(int, char*[])
{
    try
    {
        throw myException{ "Something Happened" };
    }
    catch (myException &e)
    {
        std::cout << e.what() << std::endl;
    }
    return{ 0 };
}

As Kilian points out in the comment section the example depends on a specific implementation of std::exception that offers more constructors than are mentioned here.

In order to avoid that you can use any of the convenience classes predefined in the header <stdexcept>. See these «Exception categories» for inspiration.

answered Jul 12, 2018 at 20:18

Johannes's user avatar

JohannesJohannes

6,4308 gold badges58 silver badges107 bronze badges

2

The what method is virtual, and the meaning is that you should override it to return whatever message you want to return.

answered Nov 16, 2011 at 13:54

Some programmer dude's user avatar

0

Here is an example

 class CommunicationError: public std::exception {
  public:
   explicit CommunicationError(const char* message) : msg(message) {}
   CommunicationError(CommunicationError const&) noexcept = default;

   CommunicationError& operator=(CommunicationError const&) noexcept = default;
  ~CommunicationError() override = default;

  const char* what() const noexcept override { return msg; }
 private:
  const char* msg;
};

[1] https://www.autosar.org/fileadmin/user_upload/standards/adaptive/17-03/AUTOSAR_RS_CPP14Guidelines.pdf

answered Oct 6, 2021 at 8:28

rjhcnf's user avatar

rjhcnfrjhcnf

7746 silver badges9 bronze badges

Though this question is rather old and has already been answered plenty, I just want to add a note on how to do proper exception handling in C++11, since I am continually missing this in discussions about exceptions:

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace!
You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

You don’t even need to subclass std::runtime_error in order to get plenty of information when an exception is thrown.

The only benefit I see in subclassing (instead of just using std::runtime_error) is that your exception handler can catch your custom exception and do something special. For example:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}

Наследование исключений

Определённый пользователем класс исключения должен быть определён, как
класс расширяющий (наследующий) встроенный класс Exception. Ниже приведены методы и
свойства класса Exception, доступные дочерним классам.

Пример #1 Встроенный класс Exception


<?php
class Exception implements Throwable
{
protected
$message = 'Unknown exception'; // сообщение об исключении
private $string; // свойство для __toString
protected $code = 0; // пользовательский код исключения
protected $file; // файл, в котором было выброшено исключение
protected $line; // строка, в которой было выброшено исключение
private $trace; // трассировка вызовов методов и функций
private $previous; // предыдущее исключение, если исключение вложенноеpublic function __construct($message = '', $code = 0, Throwable $previous = null);

final private function

__clone(); // запрещает клонирования исключенияfinal public function getMessage(); // сообщение исключения
final public function getCode(); // код исключения
final public function getFile(); // файл, где выброшено исключение
final public function getLine(); // строка, на которой выброшено исключение
final public function getTrace(); // массив backtrace()
final public function getPrevious(); // предыдущее исключение
final public function getTraceAsString(); // отформатированная строка трассировки

// Переопределяемый

public function __toString(); // отформатированная строка для отображения
}
?>

Если класс, наследуемый от Exception переопределяет конструктор, необходимо вызвать в
конструкторе parent::__construct(), чтобы быть уверенным, что все доступные данные
были правильно присвоены. Метод __toString()
может быть переопределён, чтобы обеспечить нужный вывод, когда объект
преобразуется в строку.

Замечание:

Исключения нельзя клонировать.
Попытка клонировать
исключение приведёт к неисправимой ошибке E_ERROR.

Пример #2 Наследование класса Exception


<?php
/**
* Определим свой класс исключения
*/
class MyException extends Exception
{
// Переопределим исключение так, что параметр message станет обязательным
public function __construct($message, $code = 0, Throwable $previous = null) {
// некоторый код

// убедитесь, что все передаваемые параметры верны

parent::__construct($message, $code, $previous);
}
// Переопределим строковое представление объекта.
public function __toString() {
return
__CLASS__ . ": [{$this->code}]: {$this->message}n";
}

public function

customFunction() {
echo
"Мы можем определять новые методы в наследуемом классеn";
}
}
/**
* Создадим класс для тестирования исключения
*/
class TestException
{
public
$var;

const

THROW_NONE = 0;
const
THROW_CUSTOM = 1;
const
THROW_DEFAULT = 2;

function

__construct($avalue = self::THROW_NONE) {

switch (

$avalue) {
case
self::THROW_CUSTOM:
// Выбрасываем собственное исключение
throw new MyException('1 - неправильный параметр', 5);
break;

case

self::THROW_DEFAULT:
// Выбрасываем встроеное исключение
throw new Exception('2 - недопустимый параметр', 6);
break;

default:

// Никаких исключений, объект будет создан.
$this->var = $avalue;
break;
}
}
}
// Пример 1
try {
$o = new TestException(TestException::THROW_CUSTOM);
} catch (
MyException $e) { // Будет перехвачено
echo "Поймано собственное переопределённое исключениеn", $e;
$e->customFunction();
} catch (
Exception $e) { // Будет пропущено
echo "Поймано встроенное исключениеn", $e;
}
// Отсюда будет продолжено выполнение программы
var_dump($o); // Null
echo "nn";// Пример 2
try {
$o = new TestException(TestException::THROW_DEFAULT);
} catch (
MyException $e) { // Тип исключения не совпадёт
echo "Поймано переопределённое исключениеn", $e;
$e->customFunction();
} catch (
Exception $e) { // Будет перехвачено
echo "Перехвачено встроенное исключениеn", $e;
}
// Отсюда будет продолжено выполнение программы
var_dump($o); // Null
echo "nn";// Пример 3
try {
$o = new TestException(TestException::THROW_CUSTOM);
} catch (
Exception $e) { // Будет перехвачено
echo "Поймано встроенное исключениеn", $e;
}
// Продолжение исполнения программы
var_dump($o); // Null
echo "nn";// Пример 4
try {
$o = new TestException();
} catch (
Exception $e) { // Будет пропущено, т.к. исключение не выбрасывается
echo "Поймано встроенное исключениеn", $e;
}
// Продолжение выполнения программы
var_dump($o); // TestException
echo "nn";
?>

iamhiddensomewhere at gmail dot com

13 years ago


As previously noted exception linking was recently added (and what a god-send it is, it certainly makes layer abstraction (and, by association, exception tracking) easier).

Since <5.3 was lacking this useful feature I took some initiative and creating a custom exception class that all of my exceptions inherit from:

<?phpclass SystemException extends Exception
{
    private
$previous;

        public function

__construct($message, $code = 0, Exception $previous = null)
    {
       
parent::__construct($message, $code);

                if (!

is_null($previous))
        {
           
$this -> previous = $previous;
        }
    }

        public function

getPrevious()
    {
        return
$this -> previous;
    }
}
?>

Hope you find it useful.


sapphirepaw.org

13 years ago


Support for exception linking was added in PHP 5.3.0. The getPrevious() method and the $previous argument to the constructor are not available on any built-in exceptions in older versions of PHP.

Hayley Watson

4 years ago


Check the other SPL Exception classes and extend one of those if your intended exception is a subclass of one of those. This allows more finesse when catching.

michaelrfairhurst at gmail dot com

10 years ago


Custom exception classes can allow you to write tests that prove your exceptions
are meaningful. Usually testing exceptions, you either assert the message equals
something in which case you can't change the message format without refactoring,
or not make any assertions at all in which case you can get misleading messages
later down the line. Especially if your $e->getMessage is something complicated
like a var_dump'ed context array.

The solution is to abstract the error information from the Exception class into
properties that can be tested everywhere except the one test for your formatting.

<?phpclass TestableException extends Exception {

        private

$property;

        function

__construct($property) {$this->property = $property;
               
parent::__construct($this->format($property));

        }

        function

format($property) {
                return
"I have formatted: " . $property . "!!";
        }

        function

getProperty() {
                return
$this->property;
        }

}

function

testSomethingThrowsTestableException() {
        try {
                throw new
TestableException('Property');
        } Catch (
TestableException $e) {
               
$this->assertEquals('Property', $e->getProperty());
        }
}

function

testExceptionFormattingOnlyOnce() {
       
$e = new TestableException;
       
$this->assertEquals('I have formatted: properly for the only required test!!',
               
$e->format('properly for the only required test')
        );
}
?>


Dor

11 years ago


It's important to note that subclasses of the Exception class will be caught by the default Exception handler

<?php/**
     * NewException
     * Extends the Exception class so that the $message parameter is now mendatory.
     *
     */
   
class NewException extends Exception {
       
//$message is now not optional, just for the extension.
       
public function __construct($message, $code = 0, Exception $previous = null) {
           
parent::__construct($message, $code, $previous);
        }
    }
/**
     * TestException
     * Tests and throws Exceptions.
     */
   
class TestException {
        const
NONE = 0;
        const
NORMAL = 1;
        const
CUSTOM = 2;
        public function
__construct($type = self::NONE) {
            switch (
$type) {
                case
1:
                    throw new
Exception('Normal Exception');
                    break;
                case
2:
                    throw new
NewException('Custom Exception');
                    break;
                default:
                    return
0; //No exception is thrown.
           
}
        }
    }

        try {

$t = new TestException(TestException::CUSTOM);
    }
    catch (
Exception $e) {
       
print_r($e); //Exception Caught
   
}?>

Note that if an Exception is caught once, it won't be caught again (even for a more specific handler).


shaman_master at list dot ru

8 years ago


Use this example for not numeric codes:
<code>
<?php
class MyException extends Exception
{
   
/**
     * Creates a new exception.
     *
     * @param string       $message   Error message
     * @param mixed       $code         The exception code
     * @param Exception $previous  Previous exception
     * @return void
     */
   
public function __construct($message = '', $code = 0, Exception $previous = null)
    {
       
// Pass the message and integer code to the parent
       
parent::__construct((string)$message, (int)$code, $previous);// @link http://bugs.php.net/39615 Save the unmodified code
       
$this->code = $code;
    }
}
</
code>

florenxe

7 years ago


I just wanted to add that "extends" is same concept of "Inheritance" or "Prototyping in Javascript". So when you extend a class, you are simply inheriting the class's methods and properties. So you can create custom classes from existing classes like extending the array class.

paragdiwan at gmail dot com

14 years ago


I have written similar simple custom exception class. Helpful for newbie.

<?php

   
/*

        This is written for overriding the exceptions.

        custom exception class

    */

   
error_reporting(E_ALL-E_NOTICE);

    class
myCustomException extends Exception

   
{

       
        public function

__construct($message, $code=0)

        {

           
parent::__construct($message,$code);

        }   

        public function

__toString()

        {

            return
"<b style='color:red'>".$this->message."</b>";

        }

       
       
    }

    class

testException

   
{

       
        public function

__construct($x)

        {
$this->x=$x;

           
        }

       
        function

see()

        {

           

            if(

$this->x==9 )

            {

                throw new
myCustomException("i didnt like it");

            }

        }

    }
$obj = new testException(9);

    try{
$obj->see();

    }

    catch(
myCustomException $e)

    {

        echo
$e;

    }

?>

  • Каким образом проводится экстраполяция ошибки выборки на генеральную совокупность
  • Каким образом можно уменьшить влияние ошибок восприятия на общение кратко
  • Каким образом можно избежать ошибки при каузальной атрибуции
  • Каким образом могут быть устранены ошибки допущенные при принятии решения суда
  • Каким образом исправляются ошибки допущенные при составлении первичной документации тест