Реализация конструктора копирования для exception

Вглядываясь в доки к std::exception, обнаруживается, что конструктор копирования исключения (std::exception(const std::exception&)), декларирован, как noexcept. Более того, стандарт требует, чтобы каждое исключение в стандарте декларировало конструктор копирования как noexcept. Не понимаю, как это вообще возможно реализовать, ведь исключение по идеи должно хранить внутри себя что-то вроде std::string, чтобы сохранять сообщение от пользователя, когда кидается исключение, и, вследствии этого, копировать эту самую строку при копировании исключения. Но в таком случае может быть нарушено обещание noexcept, когда конструктор std::string кидает std::bad_alloc.

Подскажите, пожалуйста, как реализовать класс исключения так, чтобы конструктор копирования исключения не нарушал обещание noexcept


Ответы (1 шт):

Автор решения: Serge3leo

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

Если пояснительная строка должна создаваться динамически, как в иерархиях исключений std::logic_error и std::runtime_error, то, ввиду динамического создания пояснительной строки, принято идти на компромисс и допускать возможность исключений в конструкторе. Под капотом стандартных реализаций обычно используются: "строки с подсчётом ссылок", "строки Copy-On-Write" и т.п.

Если Вам такой компромисс подходит, то логично не изобретать велосипед, а использовать наследника от std::logic_error в качестве корня вашей иерархии исключений.

В случае же, сравнительной жесткости ваших требований к надёжности и/или эффективности, но при необходимости задавать поясняющую строку литералом по месту, то в качестве корня вашей иерархии возможен компромисс типа:

class my_exception_base : public std::exception {
    const char *_msg;
public:
    template <size_t N>
    explicit my_exception_base(const char (&s)[N]) noexcept {
        _msg = s;
    }
    char const * what() const noexcept {
        return _msg;
    }
};

Строго говоря, этот класс слишком простой и, хотя шаблон блокирует основные потенциальные ошибки, его можно использовать некорректно. К примеру, извернуться и передать в него char x[] = "bad msg"; со стека (при изощрённом метапрограммировании C++, в принципе, можно блокировать всё, кроме литералов, но...).

→ Ссылка