Флажки / переключатели функций, когда артефакт является библиотекой и флаги влияют на заголовки C или C ++

Существует довольно много дискуссий о функция флаги / переключатели а также почему вы бы их использовали но большая часть обсуждения их реализации сосредоточена вокруг (веб или клиентских) приложений. Если ваш продукт / артефакт представляет собой библиотеку C или C ++ и флаги затрагивают ваши публичные заголовки, как бы вы их реализовали?

«Наивный» способ сделать это не работает:

/// Does something
/**
* Does something really cool
#ifdef FEATURE_FOO
* @param fooParam describe param for foo
#endif
*/
void doSomethingCool(
#ifdef FEATURE_FOO
int fooParam = 42
#endif
);

Вы не хотели бы грузить что-то подобное.

  • Ваша библиотека, которую вы отправляете, была создана для определенной комбинации флагов, клиентам не нужно #define одни и те же флаги функций, чтобы заставить вещи работать
  • Ifdefs в вашем публичном заголовке ужасны
  • И, самое главное, если вы отключите свой флаг, вы не хотите, чтобы клиенты видели что-либо об отключенных функциях — может быть, это что-то грядущее, и вы не хотите показывать свои вещи, пока они не будут готовы

Запуск препроцессора для файла, чтобы получить заголовок для распространения, на самом деле не работает, потому что это не только влияет на флаги функций, но и делает все остальное, что делает препроцессор.

Каким было бы техническое решение для этого, у которого нет этих недостатков?

4

Решение

Этот вид слизи оказывается в кодовой базе из-за управления версиями. Широкая тема с очень немногими счастливыми ответами. Но вы, конечно же, хотите избежать того, чтобы сделать это более сложным, чем нужно. Сосредоточиться на Добрый совместимости, которую вы хотите обеспечить.

Синтаксис, предложенный во фрагменте, требуется только тогда, когда вам нужно двоичный совместимость. Это поддерживает библиотеку, совместимую с вызовом doSomethingCool () в клиентском коде (без передачи аргумента) без необходимости компилировать этот клиентский код. Другими словами, клиентский программист ничего не делает, кроме копирования обновленного файла .dll или .so, не нуждается в каких-либо обновленных заголовках, и только ваша задача — правильно установить флаги функций. Бинарную совместимость довольно сложно осуществить надежно, за пределами спорных моментов, легко ошибиться.

Но на самом деле вы говорите о совместимости исходного кода, вы предоставляете пользователю обновленный заголовок, и он перестраивает свой код, чтобы использовать обновление библиотеки. В каком случае вы не Если нужен флаг функции, то компилятор C ++ сам по себе гарантирует передачу аргумента, он будет равен 42. Флаг вообще не требуется ни на вашей стороне, ни на стороне пользователя.

Другой способ сделать это — обеспечить перегрузку. Другими словами, функции doSomethingCool () и doSomethingCool (int). Клиентский программист продолжает использовать исходную перегрузку, пока не будет готов двигаться дальше. Вы также поддерживаете перегрузку, когда тело функции должно слишком сильно изменяться. Если эти функции не являются виртуальными, то они даже обеспечивают совместимость каналов, что может быть полезно в некоторых случаях выбора. Флаги функций не требуются.

2

Другие решения

Я бы сказал, что это довольно широкий вопрос, но я добавлю два цента.

Во-первых, вы действительно хотите отделить публичные заголовки от реализации (исходные и внутренние заголовки, если таковые имеются). Открытый заголовок, который устанавливается (например, в /usr/include) должен содержать объявление функции и, предпочтительно, постоянное логическое значение для информирования клиента о том, есть ли в библиотеке определенная функция, скомпилированная или нет, следующим образом:

#define FEATURE_FOO 1
void doSomethingCool();

Такой заголовок обычно генерируется. Autotools является де-факто стандартные инструменты для этой цели в GNU / Linux. В противном случае вы можете написать свои собственные сценарии для этого.

Для полноты, в файле .c вы должны иметь

void doSomethingCool(
#ifdef FEATURE_FOO
int fooParam = 42
#endif
);

Ваши инструменты распространения также должны поддерживать синхронизацию установленных заголовков и двоичных файлов библиотеки.

2

Используйте предварительные декларации

Скрыть реализацию с помощью указателя (идиома Pimpl)

этот код, указанный в предыдущей ссылке:

// Foo.hpp
class Foo {
public:

//...

private:
struct Impl;
Impl* _impl;
};

// Foo.cpp
struct Foo::Impl {
// stuff
};
0

Бинарная совместимость не является сильной стороной C ++, ее, вероятно, не стоит рассматривать.
Для C вы можете создать что-то вроде интерфейсного класса, так что ваше первое прикосновение к библиотеке будет примерно таким:

struct kv {
char *tag;
int   val;
};
int Bind(struct kv *compat, void **funcs, void **stamp);

и ваш доступ к библиотеке теперь:

#define MyStrcpy(src, dest)  (funcs->mystrcpy((stamp)(src),(dest)))

Контракт заключается в том, что Bind предоставляет / создает соответствующую пару (func, stamp) для предоставленного вами набора атрибутов; или терпит неудачу, если не может. Обратите внимание, что Bind — это единственный бит, который должен знать о множественных раскладках * funcs, * stamp; таким образом, он может прозрачно обеспечить надежный интерфейс для этой уменьшенной версии проблемы.

Если вы хотите стать по-настоящему модным, вы можете достичь того же, переписав PLT, подготовленный для вас dlopen / dlsym, но:

  1. Вы значительно расширяете свою поверхность атаки.
  2. Вы добавляете много сложности для очень небольшого усиления.
  3. Вы добавляете код, специфичный для платформы / архитектуры, где ни один не гарантирован.

Осталось несколько минусов. Вы должны вызвать Bind до того, как какая-либо часть вашей программы / библиотеки попытается его использовать. Попытки решить что ведут прямо в ад (Нахождение C ++ статических проблем порядка инициализации), что должно заставить Н. Вирта улыбнуться. Если вы слишком умны с Bind (), вам будет жаль, что вы этого не сделали. Возможно, вы захотите быть осторожнее с повторным входом, поскольку данный клиент может связываться несколько раз для разных наборов атрибутов (такие проблемы у пользователей).

0

Вот как бы я справился с этим на чистом C.

Прежде всего, я бы упаковал их в одно целое без знака длиной 32/64, чтобы сделать их максимально компактными.

Второй шаг — закрытый заголовок для использования только при компиляции библиотеки, где я бы определил макрос для создания оболочки функции API и внутреннюю функцию:

#define CoolFeature1 0x00000001    //code value as 0 to disable feature
#define CoolFeature2 0x00000010
#define CoolFeature3 0x00000100
.... // Other features

#define Cool CoolFeature1 | CoolFeature2 | CoolFeature3 | ... | CoolFeature_n

#define ImplementApi(ret, fname, ...)    ret fname(__VA_ARGS__)  \
{ return Internal_#fname(Cool, __VA_ARGS__);}  \
ret Internal_#fname(unsigned long Cool, __VA_ARGS__)
#include "user_header.h"    //Include the standard user header where there is no reference to Cool features

Теперь у нас есть оболочка со стандартным прототипом, который будет доступен в заголовке определения пользователя, и внутренняя версия, в которой есть группа флагов добавления для указания дополнительных функций.

При кодировании с использованием макроса вы можете написать:

ImplementApi(int, MyCoolFunction, int param1, float param2, ...)
{
// Your code goes here
if (Cool & CoolFeature2)
{
// Do something cool
}
else
{
// Flat life ...
}
...
return 0;
}

В приведенном выше случае вы получите 2 определения:

int Internal_MyCoolFunction(unsigned long Cool, int param1, float param2, ...);
int MyCoolFunction(int param1, float param2, ...)

В конце концов, вы можете добавить в макрос для функции API атрибуты для экспорта, если вы распространяете динамическую библиотеку.

Вы даже можете использовать тот же заголовок определения, если определение ImplementApi макрос выполняется в командной строке компилятора, в этом случае подойдет следующее простое определение в заголовке:

#define ImplementApi(ret, fname, ...)    ret fname(__VA_ARGS__);

Последний будет генерировать только экспортированные прототипы API.

Это предложение, конечно, не является исчерпывающим. Существует множество дополнительных настроек, которые можно сделать для более элегантных и автоматических определений. То есть включая подзаголовок со списком функций для создания только прототипов функций API для пользователя, а также внутреннего и API для разработчиков.

0
По вопросам рекламы [email protected]