Владеет ли 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 возможно, что вы не можете создать новый строковый объект). Вот почему исключения лучше отлавливать по ссылке, а не по значению.