Владеет ли std:: исключение чем?

Я получаю свое собственное исключение, назовите это MyException, от std::system_error и переопределил what() рассчитать и вернуть мое сообщение. MyExceptionСписок инициализаторов не вызывает переопределение конструктора system_error, которое принимает сообщение.

Если я поймаю MyException и скопировать его в std::exception результат звонка what() на std::exception является nullptr, Это имеет смысл.

У меня вопрос, если я использую конструктор system_exception, который принимает сообщение при инициализации MyException, указано, что system_error возьмет копию сообщения и будет владельцем и освободит ее?

Я предполагаю, что это позволило бы std::exception копия MyException чтобы иметь возможность вернуть действительный what(), Хотя я бы оценил производительность в том, что "что" нужно вычислять каждый раз, когда новый MyExceptions создано; Я не могу лениво вычислить это только тогда, когда то, что () сначала вызывается.

Я немного обеспокоен владением строкой "что", как what() возвращает char* и не const std::string&,

Код выглядит примерно так (я не скомпилировал это):

    class MyException : public std::system_error
    {
        std::string what_;
    public:
        MyException(int errorValue, const std::error_category& category)
            : std::system_error(errorValue, category)
        {}

        char* what() const
        {
           what_ = "MyException: " + to_string(code().value());
           return what_.c_str();
        }
    };

    int main()
    {
        std::exception ex;

        try
        {
            throw MyException(4, system_category());
        }
        catch( const MyException& e )
        {
            ex = e;
        }

        printf("what= %s", ex.what());

        return 1;
    }

3 ответа

Решение

У меня вопрос, если я использую конструктор system_exception, который принимает сообщение при инициализации MyException, указано, что system_error возьмет копию сообщения и будет владельцем и освободит ее?

Да, это гарантировано стандартом.

Начать, std::exception не владеет what - std::runtime_error делает. std::runtime_errorКонструкторы определены следующим образом ([runtime.error]p2-5):

runtime_error(const string& what_arg);

Эффекты: Создает объект класса runtime_error,
Постусловие: strcmp(what(), what_arg.c_str()) == 0,

runtime_error(const char* what_arg);

Эффекты: Создает объект класса runtime_error,
Постусловие: strcmp(what(), what_arg) == 0,

Таким образом, он должен хранить копию what_arg внутренне, поскольку нет никаких требований относительно срока действия значения, переданного в.

Далее есть [исключение] р2:

Каждый стандартный класс библиотеки T что происходит от класса exception должен иметь общедоступный конструктор копирования и общедоступный оператор присваивания копии, которые не выходят за исключением. Эти функции-члены должны соответствовать следующему постусловию: если два объекта lhs а также rhs оба имеют динамический тип T а также lhs является копией rhs, затем strcmp(lhs.what(), rhs.what()) должен быть равен 0,

Таким образом, должен существовать конструктор копирования, он никогда не должен выдавать, и копии должны поддерживать одинаковое возвращаемое значение для what(), Аналогично для оператора копирования-назначения.

Собрав все это вместе, мы можем предположить, что std::runtime_error должен сохранить значение, которое вы передаете what_arg внутренне в строке с подсчетом ссылок (чтобы избежать исключений из выделений при копировании), и значение будет сохраняться независимо от копирования и / или нарезки - но только до std::runtime_errorне до std::exception! (Более подробная информация об обоснованиях и требованиях, касающихся what Howard Hinnant можно найти в этом очень интересном ответе от Howard Hinnant: конструктор перемещения для std:: runtime_error)

std::system_errorнаследуется отstd::runtime_error Таким образом, все то же самое относится и к нему, и к любому производному от него типу (при условии, что производный тип поддерживает инвариант конструктора копирования без отбрасывания).

Я предполагаю, что это позволило бы std::exceptionкопияMyExceptionчтобы иметь возможность вернуть действительныйwhat(),

Нет! Когда вы делаетеstd::exception копияMyException вы нарезаете объект до менее производного типа, чем гдеwhat Значение физически сохраняется. Если вы должны сделать копию своего исключения, наименее производным типом, который вы можете использовать, являетсяstd::runtime_error, (Вы всегда можете безопасно сделатьstd::exception ссылка наMyException Конечно.) Другими словами, невозможно получить значимую строку из std::exception объектwhat(),


Этот код имеет поведение, которое вы хотите, переносимо:

#include <cstdio>
#include <stdexcept>
#include <system_error>
#include <string>

class MyException : public std::system_error {
public:
    MyException(int errorValue, std::error_category const& category)
      : std::system_error(
            errorValue, category,
            "MyException: " + std::to_string(errorValue)
        )
    { }
};

int main() {
    std::runtime_error ex;

    try {
        throw MyException(4, system_category());
    } catch(MyException const& e) {
        ex = e;
    }

    std::printf("what= %s", ex.what());
}

Я бы сказал, что плохо писать конструктор исключений, который выделяет (по понятным причинам), но, учитывая, что каждая текущая стандартная реализация библиотеки, о которой я знаю, использует оптимизацию коротких строк для std::basic_string<>на практике это крайне маловероятно.

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

Вы можете гарантировать, что срок действия вашего исключения будет продлен с помощью умного указателя. Я не уверен, как это повлияет на производительность, но вы, вероятно, могли бы использовать это для привязки к своему собственному расширению std::system_error и избегать копирования конструкции в целом. (На самом деле, я не гарантирую, что будет предотвращено создание копии. Создание умного указателя может копировать исключение, а может и не копировать, кажется. Но это скопировало бы ваше исключение, что должно быть правильным, если вы предоставите конструктор копирования что вы должны предоставить.) Ваша основная функция в конечном итоге будет выглядеть примерно так.

#include <exception> // std::exception_ptr

int main()
{
    std::exception_ptr p;

    try
    {
        throw MyException(4, system_category());
    }
    catch( const MyException& e )
    {
        p = std::current_exception();
    }

    try
    {
        std::rethrow_exception(p);
    }
    catch (const std::exception& e)
    {
        printf("what= %s", e.what());
    }

    return 1;
}

В основном это просто переписать пример использования указателя исключения, о котором я читал здесь на cplusplus.com, но я использовал ваш класс исключения, а не стандартное исключение, подобное std::logic_error,

Что касается вашего первоначального вопроса, кажется, что трудно сделать жесткие гарантии. Я нашел следующее утверждение в документации по оператору присваивания за исключением применительно к C++11. В C++98 даже эта гарантия не предоставляется.

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

Тем не менее, динамический тип std::system_error не будет соответствовать динамическому типу std::exception в вашем случае, так что я не думаю, что это гарантированно сработает.

Исключению класса не принадлежит ни одна строка. Когда вы разрезаете свой объект исключения, вы получаете базовый объект исключения, который не имеет переопределенной виртуальной функции what().

Волшебство функции what() заключается в виртуальной функции what() и в вашем производном классе. Вы можете передать const char *, хранящийся в статической памяти, объекту исключения, и он не будет скопирован.

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

Другие вопросы по тегам