Я хотел бы реализовать класс Exception в С++, который имитирует один из .NET framework (и Java тоже имеет нечто подобное) для следующих целей:
-
Цепочка исключений: я хотел бы реализовать концепцию "трансляции исключений", когда исключения, захваченные на более высоких уровнях, обертывают и "переводят" исключения нижнего уровня, также как-то сохраняя эти исключения уровня любовника (в
InnerException
, в этом случае). Для этого должен быть некоторый механизм для хранения внутренних исключений вместе с каждым исключением, созданным на верхнем уровне.InnerException
член предоставляет это в реализации ниже. -
Наследование исключения: должно быть возможно получить
IoException
изException
иSerialPortException
изIoException
, например. Хотя это кажется тривиальным, должна существовать возможность динамически идентифицировать тип захваченных исключений (например, для целей ведения журнала или для отображения пользователю), предпочтительно без накладных расходов RTTI иtypeid
.
Это примерная логика обработки исключений, которую я хотел бы сделать:
try
{
try
{
try
{
throw ThirdException(L"this should be ThirdException");
}
catch(Exception &ex)
{
throw SubException(L"this should be SubException", ex);
}
}
catch(Exception &ex)
{
throw SubException(L"this should be SubException again", ex);
}
}
catch(Exception &ex)
{
throw Exception(L"and this should be Exception", ex);
}
и когда ловить "внешнее большинство" исключение в самом верхнем слое, я бы хотел разобрать и форматировать всю цепочку исключений через член InnerException
, чтобы отобразить что-то вроде этого:
Я до сих пор придумал следующую реализацию:
Небольшое примечание: CString
- это строковый класс, специфичный для Microsoft (только для людей, не знакомых с материалами Visual С++).
class Exception
{
protected:
Exception(const Exception&) {};
Exception& operator= (const Exception&) {};
public:
Exception(const CString &message) : InnerException(0), Message(message) {}
Exception(const CString &message, const Exception &innerException) : InnerException(innerException.Clone()), Message(message) {}
virtual CString GetExceptionName() const { return L"Exception"; }
virtual Exception *Clone() const
{
Exception *ex = new Exception(this->Message);
ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
return ex;
}
public:
virtual ~Exception() { if (InnerException) delete InnerException; }
CString Message;
const Exception *InnerException;
};
Теперь, что мы имеем здесь. Оператор копирования и оператор присваивания сделаны protected
для предотвращения копирования. Каждый объект будет "владеть" своим внутренним объектом исключения (и удалять его в деструкторе), поэтому по умолчанию мелкое копирование было бы неприемлемым. Тогда у нас есть два довольно стандартных конструктора и виртуальный деструктор, который удаляет объект InnerException
. Clone()
виртуальный метод отвечает за глубокое копирование объектов, прежде всего для хранения внутреннего объекта исключения (см. второй конструктор). И, наконец, виртуальный метод GetExceptionName()
предоставляет дешевую альтернативу RTTI для идентификации имен классов исключений (я не думаю, что это выглядит круто, но я не мог найти лучшего решения, для сравнения: в .NET можно просто использовать someException.GetType().Name
).
Теперь это делает работу. Но... Мне не нравится это решение по одной конкретной причине: количество кодирования, необходимое для каждого производного класса. Считаю, что я должен получить класс SubException
, который обеспечивает абсолютно нулевые дополнения к функциональности базового класса, он просто предоставляет настраиваемое имя ( "SubException", которое может быть "IoException", "ProjectException",...), чтобы отличать его для его сценария использования. Я должен предоставить почти одинаковый код для каждого из таких классов исключений. Вот он:
class SubException : public Exception
{
protected:
SubException(const SubException& source) : Exception(source) {};
SubException& operator= (const SubException&) {};
public:
SubException(const CString &message) : Exception(message) {};
SubException(const CString &message, const Exception &innerException) : Exception(message, innerException) {};
virtual CString GetExceptionName() const { return L"SubException"; }
virtual Exception *Clone() const
{
SubException *ex = new SubException(this->Message);
ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
return ex;
}
};
Мне не нравится тот факт, что я должен каждый раз предоставлять конструктор и оператор присваивания protected
, мне не нравится тот факт, что я должен каждый раз клонировать метод Clone
, дублируя даже код копировать базовые элементы (InnerException
...), просто... Я не думаю, что это изящное решение. Но я не мог придумать лучшего. Есть ли у вас идеи, как реализовать эту концепцию "правильно"? Или, может быть, это лучшая реализация этой концепции, которая возможна в С++? Или, может быть, я делаю это совершенно неправильно?
PS: Я знаю, что для этой цели существуют некоторые механизмы в С++ 11 (также в Boost) (цепочка исключений) с некоторыми новыми классами исключений, но меня в первую очередь интересует пользовательский "old-С++-compatible", пути. Но было бы хорошо, кроме того, если бы кто-то мог предоставить какой-либо код на С++ 11, который выполнит то же самое.