Написание библиотеки с интерфейсами C и C++, какой способ обернуть?
При подготовке библиотеки (назовем ее libfoo) я обнаружил перед собой следующую дилемму: писать ли ее как библиотеку C++ с оболочкой C:
namespace Foo {
class Bar {
...
};
}
/* Separate C header. #ifdef __cplusplus omitted for brevity. */
extern "C" {
typedef void *FooBar;
FooBar* foo_bar_new() { return new Foo::Bar; }
void foo_bar_delete(FooBar *bar) { delete bar; }
}
Или лучше написать его как библиотеку C с оболочкой C++:
/* foo/bar.h. Again, #ifdef __cplusplus stuff omitted. */
typedef struct {
/* ... */
} FooBar;
void foo_bar_init(FooBar *self) { /* ... */ }
void foo_bar_deinit(FooBar *self) { /* ... */ }
/* foo/bar.hpp */
namespace Foo {
class Bar {
/* ... */
FooBar self;
}
Bar::Bar() {
foo_bar_init(&self);
}
Bar::~Bar() {
foo_bar_deinit(&self);
}
}
Что вы предпочитаете и почему? Я предпочитаю последнее, потому что это означает, что мне не нужно беспокоиться о том, что в моих функциях C случайно всплывают исключения, плюс я предпочитаю C как язык, так как считаю, что это меньшее семантическое минное поле. Что думают другие люди?
РЕДАКТИРОВАТЬ: так много хороших ответов. Спасибо всем. Обидно, что я могу принять только один.
9 ответов
Маленькие очки:
Когда вы пишете библиотеку C, она полезна где угодно - в C, в C++ (с оберткой) и во многих других языках, таких как Python, Java с использованием привязок и т. Д., И что наиболее важно, она требует только времени выполнения C.
Когда вы пишете оболочку C++, вам также нужно написать оболочку C, но это не так просто, как вы думаете, например:
c_api.h:
extern "C" {
typedef void *Foo;
Foo create_foo();
}
c_api.cpp:
void *create_foo()
{
return new foo::Foo();
}
Что случилось? это может бросить! и программа завершится сбоем, так как C не имеет семантики разматывания стека. Так что вам нужно что-то вроде:
void *create_foo()
{
try {
return new foo::Foo();
}
catch(...) { return 0; }
}
И это для каждой функции C++ API.
Поэтому я думаю, что написание библиотеки C и предоставление отдельной оболочки C++ - лучшее решение.
Также это не потребовало бы связывания с библиотекой времени выполнения C++.
Пишите библиотеку на том языке, на котором вы предпочитаете писать библиотеки. Технически не имеет большого значения, каким образом вы обернетесь. Хотя некоторые C-проекты могут быть нацелены на исключение библиотек, не являющихся C, в то время как для C++-проекта было бы странным исключать библиотеки, написанные на C, это скорее философское возражение, чем практическое.
Обертывание C в оболочку C++, вероятно, приведет к несколько большей обертке, но будет более приемлемым для программистов на C.
Обратите внимание, что если вы распространяете двоичные файлы, простота C является преимуществом.
Если вы предпочитаете писать на C, зачем вам обертка C++? Клиент C++ может использовать интерфейс API в стиле C. С другой стороны, если вы предпочитаете C++, необходимо иметь обертку C для клиентов C.
Если ваша библиотека когда-либо будет распространяться в виде двоичного + заголовка (вместо отправки исходного кода), вы обнаружите, что API C более универсален, поскольку C обычно является наименьшим общим API на любой платформе.
Вот почему мне обычно приходилось создавать API-интерфейсы C с встроенными оболочками C++ вокруг них для проектов, которые я делал в последнее десятилетие и нуждался в API. Поскольку все программы были на C++, это означало, что мне пришлось создать API-оболочку C вокруг кода C++, просто чтобы обернуть вокруг него еще один API-интерфейс C++.
Предполагая компиляцию без оптимизации во время компоновки, компилятор C не может встроить функции-оболочки, поскольку он не знает, как обрабатывать вызовы C++, но компилятор C++ может легко встроить вызовы C.
Поэтому было бы неплохо создать оболочку C++ для библиотеки C, а не наоборот.
Лично я предпочитаю написать это на C++, а затем выставить интерфейс C, используя обертку. Главным образом потому, что я предпочел бы писать на надлежащем ОО-языке. Я бы тоже использовал оболочку C в стиле OO, вот что я обрисовал в общих чертах в этом посте. Я написал довольно подробное объяснение того, что вам нужно для вызова OO C++ из C в этом посте. Разработка API-оболочки C для объектно-ориентированного кода C++
Если вы чувствуете себя комфортно при написании своей библиотеки на C, то сделайте это. Она будет более переносимой как библиотека C и не будет иметь проблем с исключениями, как вы упомянули. Редко начинать с библиотеки C++ и оборачивать ее в C.
Лично я предпочитаю использовать C++ и перенести его на C. Но на самом деле это дело вкуса, и вам придется самим решать, как вам это понравится. Если вы чувствуете себя более комфортно при написании библиотеки на C, то сделайте это и оберните ее для C++.
Об исключениях: Вы можете перехватить их в каждой функции, обернутой для C, и вернуть для них код ошибки, например, имея собственный класс исключений, который уже имеет числовое значение кода ошибки, которое вы можете вернуть своим функциям C, другие, которые могут иметь брошенные любыми другими библиотеками могут быть переведены на что-то другое, однако вы должны были поймать их раньше в любом случае.
Это также зависит от того, что вы планируете использовать в своей библиотеке. Если он, в свою очередь, может извлечь большую пользу из других библиотек C++, то используйте C++.
Можно также утверждать, что если ваша библиотека будет очень большой (внутренне, не обязательно с точки зрения API), ее будет проще реализовать в C++. (Это не моя чашка чая, я предпочитаю C, но некоторые люди клянутся C++.)
Также имейте в виду, что C++ использует среду выполнения, которая в значительной степени требует операционной системы, для поддержки исключений.
Если вы предполагаете, что ваша библиотека будет использоваться в качестве основы для операционной системы или для использования в средах без операционной системы, вы должны либо знать, как отключить поддержку исключений, избегая большого (всего?) STL и предоставить свой собственный распределитель и освобождающий Это не невозможно, но вы должны точно знать, что вы делаете.
С больше подходит для тех вещей низкого уровня.