Как раз таки не через Си-код. А в обход его. Задача во-первых не допустить попадания в Си-код исключений и нарушения контракта. Во-вторых оставить для пользователя библиотеки возможность их генерировать (в моем случае неизбежно, потому что код уже написан).
Смысл «протаскивания» в том, что обработка исключения должна быть в том месте, где это нужно. Если переносить всю обработку в код враппера — это будет прямое нарушение инкапсуляции. Враппер просто не может знать об особенностях клиентского кода.
Ну так а я не говорю про С. Я говорю для С++, почему бы не прочитать вопрос habrahabr.ru/blogs/cpp/100623/#comment_3113697, прежде чем дискуссию начинать? :) Я объяснял, что код который вызывается из Си не защищен от С++-исключений. В случае callback`ов в Си библиотеках, это тоже самое, что если бы код на Си вызывал С++ код.
Причем я как раз отметил чем это чревато и почему нельзя такого допускать.
В чем идея? Есть либина XML, к ней есть С++ враппер. С++ враппер С++ библиотека? Да. Почему бы не использовать вместе с ней исключения. Однако в недрах враппера юзаются коллбэки, который выполняются в Си коде. Потому что враппер — над С-XML-либиной. Так как разрешить проблему? СС одной стороны как бы юзер враппера не виноват, он же юзает С++, с другой стороны ему вдруг нельзя использовать исключения. Это решение этой проблемы.
Да, основная идея в том, чтобы код, который использует MyLib не вынуждать отказываться от исключений. Чтобы этот код претерпел как можно меньше изменений. Для этого надо их где-то запомнить, а прежде чем запомнить, надо их отфильтровать.
Шаблоны — для удобстве фильтрации. Если в mylib не один callback, а 10, и в каждом callback`е написано нечто вроде: try
{
this_->callback();
}
catch(exception1)
{
}
catch(exceptionN)
{
}
Особенно если нужно добавить новое исключение, все это превращается в жуткую кашу. А у меня достаточно в одном месте дописать нужный тип в список исключений.
> Я не зря привел в пример expat. Проблема исключений встала именно при использовании этой
> библиотеки. Задача состояла в следующем:
> * Есть некий код, который раньше использовал библиотеку xml, написанную на С++ и этот код
> активно применял исключения;
> * Есть необходимость заменить библиотеку xml на другую без переписывания остального кода.
В данном контексте с ним все в порядке. Я сам передал указатель this, скастив его к void*, сам его же и забрал и скастил обратно. pureCLibSetUserData(this);
Так он и не знает. В этом и проблема. И при исключении функция не возращает никакого результата. Она прерывается альтернативно с раскруткой стека. Как это реализовано конкретно — зависит от компилятора.
> А зачем бросила?
Предлагается решение, при котором такого вопроса бы не возникало. Исключение не должно покидать пределы С++.
> Предлагается костыль для кривых библиотек?
В C++ исключения — это нормальное явление, new кидает исключение, dynamic_cast кидает искючения, STL кидает исключения. Кривизна библиотеки будет только тогда, когда она позволит исключению попасть в чужую среду.
Ну, давайте немного упростим ситуацию. Представьте, что вы пишете на языке Си и подключаете к проекту библиотеку написанную на С++. Библиотека эта, предположим, экспортирует функции, помеченные как extern «C», то есть такие функции свободно можно из Си вызывать. Так вот. Теперь представьте, что одна из таких функций бросила исключение. Куда оно попадет? И какие в Си есть возможности для его перехвата?
Пример на Си: void foo(int (*callback)(char *))
{
char * buffer;
int ret;
buffer = malloc(100000);
ret = callback(buffer);
free(buffer);
}
Если сallback — это функция из С++, то она может бросить исключение. При этом будет утечка памяти, так как buffer не будет освобожден.
Конечно, без крайней необходимости прибегать к такому я бы тоже не стал. Если проектировать систему с нуля, то такие вещи нужно прогнозировать. Однако у меня был уже готовый проект, который нужно было портировать на платформу, где QtXML (именно она и есть та С++ xml-библиотека) не существовало в природе, а был только expat.
Смысл «протаскивания» в том, что обработка исключения должна быть в том месте, где это нужно. Если переносить всю обработку в код враппера — это будет прямое нарушение инкапсуляции. Враппер просто не может знать об особенностях клиентского кода.
Причем я как раз отметил чем это чревато и почему нельзя такого допускать.
В чем идея? Есть либина XML, к ней есть С++ враппер. С++ враппер С++ библиотека? Да. Почему бы не использовать вместе с ней исключения. Однако в недрах враппера юзаются коллбэки, который выполняются в Си коде. Потому что враппер — над С-XML-либиной. Так как разрешить проблему? СС одной стороны как бы юзер враппера не виноват, он же юзает С++, с другой стороны ему вдруг нельзя использовать исключения. Это решение этой проблемы.
Шаблоны — для удобстве фильтрации. Если в mylib не один callback, а 10, и в каждом callback`е написано нечто вроде:
try
{
this_->callback();
}
catch(exception1)
{
}
catch(exceptionN)
{
}
Особенно если нужно добавить новое исключение, все это превращается в жуткую кашу. А у меня достаточно в одном месте дописать нужный тип в список исключений.
> Я не зря привел в пример expat. Проблема исключений встала именно при использовании этой
> библиотеки. Задача состояла в следующем:
> * Есть некий код, который раньше использовал библиотеку xml, написанную на С++ и этот код
> активно применял исключения;
> * Есть необходимость заменить библиотеку xml на другую без переписывания остального кода.
pureCLibSetUserData(this);
//...
MyLib * this_ = reinterpret_cast<MyLib*>(userData);
Предлагается решение, при котором такого вопроса бы не возникало. Исключение не должно покидать пределы С++.
> Предлагается костыль для кривых библиотек?
В C++ исключения — это нормальное явление, new кидает исключение, dynamic_cast кидает искючения, STL кидает исключения. Кривизна библиотеки будет только тогда, когда она позволит исключению попасть в чужую среду.
Пример на Си:
void foo(int (*callback)(char *))
{
char * buffer;
int ret;
buffer = malloc(100000);
ret = callback(buffer);
free(buffer);
}
Если сallback — это функция из С++, то она может бросить исключение. При этом будет утечка памяти, так как buffer не будет освобожден.