Меню сайта
Наш опрос
Оцените мой сайт
Всего ответов: 5
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Главная » 2014 » Июль » 22 » Спецификация исключений: друг или враг?
15:20
Спецификация исключений: друг или враг?
Исторически сложилось, что разные языки программирования, поддерживающие работу с исключениями, по-разному относятся к спецификации исключений. В Java спецификации обязательны и контролируются статически, в C# и Python их вообще нет, а в C++ этот вопрос является одним из самых "сырых" мест.В двух словах о том, что такое спецификация исключений, для тех, кто не сталкивался с этим понятием. Спецификация исключений - это явное описание тех типов исключений, которые могут быть сгенерированы некой функцией. В java, языке, который поддерживает наилучшим образом спецификацию исключений, это выглядит такvoid funct() throw MyException{ throw new MyException();}MyException - это единственно возможный тип исключений, который может покинуть метод. При спецификации можно указать несколько типов исключений.C++С самого начала, идея спецификации исключений была чужда языку C++. Язык унаследовал миллиарды строк кода на языке C и, к тому времени, уже были написаны миллионы строк кода на C++, в которых не было ничего о спецификации исключений. Тем не менее, в C++ была добавлена возможность спецификаций. В своей книге "Дизайн и эволюция C++" Бьёрн Страуструп пишет о том, что спецификации, изначально могли контролироваться только во время исполнения, но, позже, были добавлены некоторые возможности для статического контроля, но, как мы увидим, этого не достаточно. В том же параграфе, Бьёрн приводит пример, который я сейчас Вам продемонстрирую. Итак, предположим, что у нас есть библиотека, написанная на языке C++, вот заголовок этой библиотеки#ifndef SHAREDCPP_H#define SHAREDCPP_H#ifdef __cplusplusextern "C" {#endifvoid foo();#ifdef __cplusplus} // extern "C"#endif#endif // SHAREDCPP_HИ её реализация#include <iostream>#include <string>extern "C" {void foo(){ std::cout << "Throw exception\n"; throw std::string("test exception");}}Я назвал эту библиотеку libsharedcpp. Я использую ОС Linux, поэтому я не писал конструкции типа __declspec(dllexport). Если Вы используете ОС MS Windows, то для компиляции примеров, Вам нужно будет добавить эти конструкции (думаю, не нужно Вас учить это делать). Для компиляции, я буду использовать компиляторы из набора GCC. Собираем эту библиотеку командойg++ -shared -fPIC sharedcpp.cpp -o libsharedcpp.soТеперь создадим библиотеку libsharedc на языке C со следующим кодом#ifndef SHAREDC_H#define SHAREDC_H#ifdef __cplusplusextern "C" {#endifvoid bar();#ifdef __cplusplus} // extern "C"#endif#endif // SHAREDC_H#include "sharedc.h"#include "sharedcpp.h"void bar(){ foo();}Как видно, библиотека libsharedc использует функцию из библиотеки libsharedcpp, поэтому, при компиляции, надо указать этот фактgcc -shared -fPIC sharedc.c -lsharedcpp -L. -o libsharedc.soТеперь создадим исполняемый модуль на языке C++.#include <string>#include <iostream>#include "sharedc.h"int main(){ try { bar(); } catch(const std::string & str) { std::cout << str << std::endl; } return 0;}Если Вы используете UNIX-like ОС, то, перед компиляцией программы, Вам следует либо поместить собранные библиотеки в место, где Ваша ОС их найдёт, например в /usr/lib, либо добавить в путь для поиска текущий каталог командойexport sharedc.h"void test() throw(std::exception){ bar();}int main(){ try { test(); } catch(const std::exception & err) { } return 0;}Я добавил функцию test, которая специфицирует исключения только стандартного типа - std::exception. Статические анализаторы не смогут определить, что функция bar генерирует исключение типа std::string, и компиляция пройдёт успешно. Но при выполнении нас ждёт ошибка.$ ./program Throw exceptionterminate called after throwing an instance of 'std::string'Аварийный остановИ даже добавление catch блока, обрабатывающего std::string, не устранит проблему , так как она возникает до момента обработки исключения, а именно - в момент выхода исключения из функции.try{ test();}catch(const std::exception & err){}catch(const std::string & err){}Таким образом, я продемонстрировал ситуацию, когда пользы от спецификации исключений меньше, чем вреда.JavaПо-другому дела обстоят с Java. В этом языке спецификация исключений контролируется статически, а компиляция в байт-код позволяет выявить все спецификации на этапе компиляции или раньше. В отличие от C++, спецификация исключений в Java носит обязательный характер, что тоже сопряжено с некоторыми трудностями.Предположим, что у нас в программе, есть базовый классpackage test;public class MyBase{ private int value; public MyBase(int value) { this.value = value; }}И от него наследуются множество наследников. Напримерpublic class MyFirst extends MyBase{ public MyFirst() { super(1); }}И, некоторый наследник содержит в себе данные, исходный код которых, не доступенpackage test;import edu.uci.ics.jung.graph.Graph;import edu.uci.ics.jung.graph.SparseMultigraph;public class MySecons extends MyFirst{ private Graph<Integer, String> graph; public MySecons() { graph = new SparseMultigraph<Integer, String>(); }}Внезапно Вам становится необходимо сделать Ваш базовый класс клонируемым и Вы добавляете переопределение метода clone и наследование от интерфейса Cloneablepackage test;public class MyBase implements Cloneable{ private int value; public MyBase(int value) { this.value = value; } @Override public Object clone() { try { MyBase base = (MyBase)super.clone(); base.value = value; return base; } catch(CloneNotSupportedException ex) { // Этого не должно произойти. return null; } }}Я задушил исключение, так как оно не может возникнуть, если класс реализует интерфейс Cloneable. Теперь я должен реализовать метод clone в потомках базового класса. С классом MyFirst проблем нет; он достаточно примитивен, чтобы не писать эту реализацию вообще. Но, вот, класс MySecond принесёт нам не мало хлопот. Склонировать граф из библиотеки JUNG мы не можем, так как он не предоставляет нам такой возможности. После некоторых раздумий, мы можем прийти к выводу, что клонировать граф - действительно не лучшая затея, и мы решаем запретить клонировать объекты класса MySecond. Но мы уже разрешили клонировать базовый класс и всех его детей, поэтому единственным верным решением остаётся выкинуть исключение при попытке вызова метода clone. Но не тут-то было. В java запрещается специфицировать исключения у переопределённых методов таким образом, чтобы это расходилось со спецификацией, определённой в родительском классе (убирать исключения из спецификации можно). И теперь, как ни старайся, ничего с методом clone, дельного не выйдет, либо возвращаем задушенное исключение, либо придумываем другой способ копирования. Я, к слову, смог ввести некое подобие конструкторов копирования из C++. Другой вариант решения проблемы описан в статье "Фабрика клонов".ИтогоЯ не могу говорить за большинство языков, которые существуют, но о некоторых сказать кое-что могу. В частности, в языках C# и Python от спецификации исключений отказались вовсе, и правильно сделали, на мой взгляд. Как видно из предыдущих частей статьи: спецификации исключений зачастую оказываются вредными, а в случае с C++, ещё и бесполезными.
Просмотров: 161 | Добавил: admin | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Форма входа
Календарь
«  Июль 2014  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей