11.3 Иерархия классов исключений
В начале этого раздела мы определили иерархию классов исключений, с помощью которой наша программа сообщает об аномальных ситуациях. В стандартной библиотеке C++ есть аналогичная иерархия, предназначенная для извещения о проблемах при выполнении функций из самой стандартной библиотеки. Эти классы исключений вы можете использовать в своих про- граммах непосредственно или создать производные от них классы для опи- сания собственных специфических исключений.
Корневой класс исключения в стандартной иерархии называется exception
. Он определен в стандартном заголовочном файле и является ба- зовым для всех исключений, возбуждаемых функциями из стандартной биб- лиотеки. Класс exception имеет следующий интерфейс: namespace std { class exception public: exception() throw(); exception( const exception & ) throw(); exception& operator=( const exception & ) throw(); virtual
exception() throw(); virtual const char* what() const throw();
};
103
}
Как и всякий другой класс из стандартной библиотеки C++, exception помещен в пространство имен std
, чтобы не засорять глобальное простран- ство имен программы.
Первые четыре функции-члена в определении класса – это конструктор по умолчанию, копирующий конструктор, копирующий оператор присваи- вания и деструктор. Поскольку все они открыты, любая программа может свободно создавать и копировать объекты-исключения, а также присваивать им значения. Деструктор объявлен виртуальным, чтобы сделать возможным дальнейшее наследование классу exception
Самой интересной в этом списке является виртуальная функция what()
, которая возвращает C-строку с текстовым описанием возбужденного ис- ключения. Классы, производные от exception
, могут заместить what()
соб- ственной версией, которая лучше характеризует объект-исключение.
Отметим, что все функции в определении класса exception имеют пу- стую спецификацию throw()
, т.е. не возбуждают никаких исключений.
Программа может манипулировать объектами-исключениями (к примеру, внутри catch
-обработчиков типа exception
), не опасаясь, что функции со- здания, копирования и уничтожения этих объектов возбудят исключения.
Помимо корневого exception
, в стандартной библиотеке есть и другие классы, которые допустимо использовать в программе для извещения об ошибках, обычно подразделяемых на две больших категории: логические ошибки и ошибки времени выполнения.
Логические ошибки обусловлены нарушением внутренней логики про- граммы, например логических предусловий или инвариантов класса. Пред- полагается, что их можно найти и предотвратить еще до начала выполнения программы. В стандартной библиотеке определены следующие такие ошибки: namespace std { class logic_error : public exception { // логическая ошибка public: explicit logic_error( const string &what_arg );
}; class invalid_argument : public logic_error { // неверный аргумент public: explicit invalid_argument( const string &what_arg );
}; class out_of_range : public logic_error { // вне диапазона public: explicit out_of_range( const string &what_arg );
}; class length_error : public logic_error { // неверная длина public:
104 explicit length_error( const string &what_arg );
}; class domain_error : public logic_error { // вне допустимой области public: explicit domain_error( const string &what_arg );
};
}
Функция может возбудить исключение invalid_argument
, если полу- чит аргумент с некорректным значением; в конкретной ситуации, когда зна- чение аргумента
выходит за пределы допустимого диапазона, разрешается возбудить исключение out_of_range
, а length_error используется для оповещения о попытке создать объект, длина которого превышает макси- мально возможную.
Ошибки времени выполнения, напротив, вызваны событием, с самой программой не связанным. Предполагается, что их нельзя обнаружить, пока программа не начала работать. В стандартной библиотеке определены сле- дующие такие ошибки: namespace std { class runtime_error : public exception { // ошибка времени выполнения public: explicit runtime_error( const string &what_arg );
}; class range_error : public runtime_error { // ошибка диапазона public: explicit range_error( const string &what_arg );
}; class overflow_error : public runtime_error { // переполнение public: explicit overflow_error( const string &what_arg );
}; class underflow_error : public runtime_error { // потеря значимости public: explicit underflow_error( const string &what_arg );
};
}
Функция может возбудить исключение range_error
, чтобы сообщить об ошибке во внутренних вычислениях. Исключение overflow_error гово- рит об ошибке арифметического переполнения, а underflow_error
– о по- тере значимости.
Класс exception является базовым и для класса исключения bad_alloc
, которое возбуждает оператор new()
, когда ему не удается выделить запро- шенный объем памяти, и для класса исключения bad_cast
, возбуждаемого в ситуации, когда ссылочный вариант оператора dynamic_cast не может быть выполнен
105
Определим оператор operator[]
в шаблоне
Array так, чтобы он возбуж- дал исключение типа range_error
, если индекс массива
Array выходит за границы:
#include
#include template class Array { public:
// ... elemType& operator[]( int ix ) const
{ if ( ix < 0 || ix >= _size )
{ string eObj =
"ошибка: вне диапазона в Array::operator[]() "; throw out_of_range( eObj );
} return _ia[ix];
}
// ... private: int _size; elemType *_ia;
};
Для использования предопределенных классов исключений в программу необходимо включить заголовочный файл. Описание возбужденного ис- ключения содержится в объекте eObj типа string
. Эту информацию можно извлечь в обработчике с помощью функции-члена what()
: int main()
{ try {
// функция main() такая же, как в разделе 16.2
} catch ( const out_of_range &excep ) {
// печатается:
// ошибка: вне диапазона в Array>elemType>::operator[]() cerr << excep.what() << "\n "; return -1;
}
}
В данной реализации выход индекса за пределы массива в функции try_array()
приводит к тому, что оператор взятия индекса operator[]()
класса
Array возбуждает исключение типа out_of_range
, которое перехва- тывается в main()
106
12. ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ Паттерны проектирования предназначены для:
эффективного решения характерных задач проектирования;
обобщенного описания решения задачи, которое можно использовать в различных ситуациях;
указания отношения и взаимодействия между классами и объектами.
Алгоритмы не являются паттернами, т.к. решают задачу вычисления, а не программирования. Они
описывают решение задачи по шагам, а не об- щий подход к ее решению.
Паттерны проектирования являются инструментами, призванными по- мочь в решении широкого круга задач стандартными методами. Что поло- жительное несет использование паттернов при проектировании программ- ных систем:
каждый паттерн описывает решение целого класса проблем;
каждый паттерн имеет известное имя;
имена паттернов позволяют абстрагироваться от конкретного алго- ритма, а решать задачу на уровне общего подхода. Это позволяет облегчить взаимодействие программистов работающих даже на разных языках про- граммирования;
правильно сформулированный паттерн проектирования позволяет, отыскав удачное решение, пользоваться им снова и снова;
шаблоны проектирования не зависят от языка программирования
(объектно-ориентированного), в отличие от идиом.
Идиома (программирование)
низкоуровневый шаблон проектирова- ния, характерный для конкретного языка программирования.
Программная идиома
выражение, обозначающее элементарную кон- струкцию, типичную для одного или нескольких языков программирования.
12.1 Порождающие паттерны проектирования Абстрагируют процесс инстанцирования. Они позволяют сделать си- стему независимой от способа создания, композиции и представления объ- ектов. Шаблон, порождающий классы,
использует наследование, чтобы из- менять инстанцируемый класс, а шаблон, порождающий объекты, делеги- рует инстанцирование другому объекту.
Инстанцирование
создание экземпляра класса. В отличие от слова «со- здание», применяется не к объекту, а к классу. То есть, говорят: «создать
107 экземпляр класса или инстанцировать класс (в виртуальной среде)». Порож- дающие шаблоны используют полиморфное инстанцирование.
Эти шаблоны оказываются важны, когда система больше зависит от ком- позиции объектов, чем от наследования классов. Основной упор делается не на жестком кодировании фиксированного набора поведений, а на определе- нии небольшого набора фундаментальных поведений, с помощью компози- ции которых можно получать любое число более сложных. Таким образом, для создания объектов с конкретным поведением требуется нечто большее, чем простое инстанцирование класса.
Порождающие шаблоны инкапсулируют знания о конкретных классах, которые применяются в системе. Они скрывают детали того, как эти классы создаются и стыкуются. Единственная информация об объектах, известная системе,
это их интерфейсы, определенные с помощью абстрактных клас- сов. Следовательно, порождающие шаблоны обеспечивают большую гиб- кость при решении вопроса о том, что создается, кто это создает, как и когда.
Иногда допустимо выбирать между тем или иным порождающим шаб- лоном. Например, есть случаи, когда с пользой для дела можно использо- вать как прототип, так и абстрактную фабрику. В других ситуациях порож- дающие шаблоны дополняют друг друга. Так, применяя строитель, можно использовать
другие шаблоны для решения вопроса о том, какие компо- ненты нужно строить, а прототип часто реализуется вместе с одиночкой.
Порождающие шаблоны тесно связаны друг с другом, их рассмотрение лучше проводить совместно, чтобы лучше были видны их сходства и разли- чия.
К порождающим паттернам проектирования относятся следующие:
абстрактная фабрика (abstract factory);
строитель (builder);
фабричный метод (factory method);
прототип (prototype);
одиночка (singleton).
12.1.1 Абстрактная фабрика Абстрактная фабрика (Abstract factory)
шаблон проектирования, позво- ляющий изменять поведение системы, варьируя создаваемые объекты, при этом сохраняя интерфейсы. Он позволяет создавать целые группы взаимо- связанных объектов, которые, будучи созданными одной фабрикой, реали- зуют общее поведение. Шаблон реализуется созданием абстрактного класса
Factory
, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса, он может создавать окна и
108 кнопки). Затем пишутся наследующиеся от него классы, реализующие этот интерфейс.
Назначение: Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов
Плюсы:
изолирует конкретные классы;
упрощает
замену семейств продуктов;
гарантирует сочетаемость продуктов.
Минусы:
сложно добавить поддержку нового вида продуктов.
Применение: Система не должна зависеть от того, как создаются, компонуются и пред- ставляются входящие в нее объекты; Входящие в семейство взаимосвязан- ные объекты должны использоваться вместе и вам необходимо обеспечить выполнение этого ограничения; Система должна конфигурироваться одним из семейств составляющих ее объектов; требуется предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию.
#include
// AbstractProductA class ICar
{ public: virtual void info() = 0;
};
// ConcreteProductA1 class Ford : public ICar
{ public: virtual void info()
{ std::cout << "Ford" << std::endl;
}
};
//ConcreteProductA2 class Toyota : public ICar
{ public: virtual void info()
{ std::cout << "Toyota" << std::endl;
}
};
// AbstractProductB class IEngine
109
{ public: virtual void getPower() = 0;
};
// ConcreteProductB1 class FordEngine : public IEngine
{ public: virtual void getPower()
{ std::cout << "Ford Engine 4.4" << std::endl;
}
};
//ConcreteProductB2 class ToyotaEngine : public IEngine
{ public: virtual void getPower()
{ std::cout << "Toyota Engine 3.2" << std::endl;
}
};
// AbstractFactory class CarFactory
{ public:
ICar* getNewCar()
{ return createCar();
}
IEngine* getNewEngine()
{ return createEngine();
} protected: virtual ICar*createCar()= 0; virtual IEngine*createEngine()= 0;
};
// ConcreteFactory1 class FordFactory : public CarFactory
{ protected:
// from CarFactory virtual ICar* createCar()
{ return new Ford();
} virtual IEngine* createEngine()
{ return new FordEngine();
}
};
// ConcreteFactory2 class ToyotaFactory : public CarFactory
{ protected:
// from CarFactory virtual ICar* createCar()
{ return new Toyota();
}
110 virtual IEngine* createEngine()
{ return new ToyotaEngine();
}
}; int main()
{
CarFactory* curFactory = NULL;
ICar* myCar = NULL;
IEngine* myEngine = NULL;
ToyotaFactory toyotaFactory;
FordFactory fordFactory; curFactory = &toyotaFactory; myCar = curFactory->getNewCar(); myCar->info(); myEngine = curFactory->getNewEngine(); myEngine->getPower(); delete myCar; delete myEngine; curFactory = &fordFactory; myCar = curFactory->getNewCar(); myCar->info(); myEngine = curFactory->getNewEngine(); myEngine->getPower(); delete myCar; delete myEngine; return 0;
}
12.1.2 Строитель (Builder)
Строитель (Builder)
шаблон проектирования, порождающий объекты.
Назначение:
Отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут полу- чаться разные представления.
Плюсы:
позволяет изменять внутреннее представление продукта;
изолирует код, реализующий конструирование и представление;
дает более тонкий контроль над процессом конструирования.
Применение:
алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой;
процесс конструирования должен обеспечивать различные представ- ления конструируемого объекта.
#include
#include
#include
111
// Product class Pizza
{ private: std::string dough; std::string sauce; std::string topping; public:
Pizza() { }
Pizza() { } void SetDough(const std::string& d) { dough = d; }; void SetSauce(const std::string& s) { sauce = s; }; void SetTopping(const std::string& t) { topping = t; } void ShowPizza()
{ std::cout << " Yummy !!!" << std::endl
<< "Pizza with Dough as " << dough
<< ", Sauce as " << sauce
<< " and Topping as " << topping
<< " !!! " << std::endl;
}
};
// Abstract Builder class PizzaBuilder
{ protected: std::auto_ptr pizza; public:
PizzaBuilder() {} virtual PizzaBuilder() {} std::auto_ptr
GetPizza() { return pizza; } void createNewPizzaProduct() { pizza.reset (new Pizza); } virtual void buildDough()=0; virtual void buildSauce()=0; virtual void buildTopping()=0;
};
// ConcreteBuilder class HawaiianPizzaBuilder : public PizzaBuilder
{ public:
HawaiianPizzaBuilder() : PizzaBuilder() {}
HawaiianPizzaBuilder(){} void buildDough() { pizza->SetDough("cross"); } void buildSauce() { pizza->SetSauce("mild"); } void buildTopping() { pizza->SetTopping("ham and pineapple"); }
};
// ConcreteBuilder class SpicyPizzaBuilder : public PizzaBuilder
{ public:
SpicyPizzaBuilder() : PizzaBuilder() {}
SpicyPizzaBuilder() {} void buildDough() { pizza->SetDough("pan baked"); } void buildSauce() { pizza->SetSauce("hot"); } void buildTopping() { pizza->SetTopping("pepperoni and salami"); }
};
// Director class Waiter
{ private:
112
PizzaBuilder* pizzaBuilder; public:
Waiter() : pizzaBuilder(NULL) {}
Waiter() { } void SetPizzaBuilder(PizzaBuilder* b) { pizzaBuilder = b; } std::auto_ptr
GetPizza() { return pizzaBuilder->GetPizza(); } void ConstructPizza()
{ pizzaBuilder->createNewPizzaProduct(); pizzaBuilder->buildDough(); pizzaBuilder->buildSauce(); pizzaBuilder->buildTopping();
}
};
// Клиент заказывает две пиццы. int main()
{
Waiter waiter;
HawaiianPizzaBuilder hawaiianPizzaBuilder; waiter.SetPizzaBuilder (&hawaiianPizzaBuilder); waiter.ConstructPizza(); std::auto_ptr pizza = waiter.GetPizza(); pizza->ShowPizza();
SpicyPizzaBuilder spicyPizzaBuilder; waiter.SetPizzaBuilder(&spicyPizzaBuilder); waiter.ConstructPizza(); pizza = waiter.GetPizza(); pizza->ShowPizza(); return EXIT_SUCCESS;
}
12.1.3 Фабричный метод (Factory Method) Фабричный метод (Factory Method)
шаблон проектирования, реализу- ющий идею «виртуального конструктора», то есть создания объектов без явного указания их типа. Относится к порождающим шаблонам проектиро- вания.
Назначение: Определяет
интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанциировать. Фабричный метод позволяет классу делегировать создание подклассам.
Плюсы:
позволяет сделать код создания объектов более универсальным, не привязываясь к конкретным классам (
ConcreteProduct
), а оперируя лишь общим интерфейсом (
Product
);
позволяет установить связь между параллельными иерархиями клас- сов.
Минусы: 113
Необходимо создавать наследника
Creator для каждого нового типа продукта (
ConcreteProduct
):
продукт (
Product
) определяет интерфейс объектов, создаваемых аб- страктным методом;
конкретный продукт (
ConcreteProduct
) реализует интерфейс
Product
;
создатель (
Creator
) объявляет фабричный метод, который возвра- щает объект типа
Product
. Может также содержать реализацию этого ме- тода «по умолчанию»; может вызывать фабричный метод для создания объ- екта типа
Product
;
конкретный создатель (
ConcreteCreator
) переопределяет фабрич- ный метод таким образом, чтобы он создавал и возвращал объект класса
ConcreteProduct
Применение:
классу заранее неизвестно, объекты каких подклассов ему нужно со- здавать.
класс спроектирован так, чтобы объекты, которые он создаёт, специ- фицировались подклассами.
класс делегирует свои обязанности одному из нескольких вспомога- тельных подклассов, и
планируется локализовать знание о том, какой класс принимает эти обязанности на себя.
#include
#include using namespace std;
// "Product" class Product{ public: virtual string getName() = 0;
};
// "ConcreteProductA" class ConcreteProductA : public Product { public: string getName() { return "ConcreteProductA";
}
};
// "ConcreteProductB" class ConcreteProductB : public Product { public: string getName() { return "ConcreteProductB";
}
}; class Creator{ public: virtual Product* FactoryMethod() = 0;
114
};
// "ConcreteCreatorA" class ConcreteCreatorA : public Creator { public:
Product* FactoryMethod() { return new ConcreteProductA();
}
};
// "ConcreteCreatorB" class ConcreteCreatorB : public Creator { public:
Product* FactoryMethod() { return new ConcreteProductB();
}
}; int main() { const int size = 2;
// An array of creators
Creator* creators[size]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB();
// Iterate over creators and create products for(int i=0;i
Product* product = creators[i]->FactoryMethod(); cout<
getName()< } int a; cin >> a; for (int i=0;i } return 0;
}