Идём ещё дальше, чем
здесьАвтор: remark
Дата: 21.01.07
Опять
[M$ SPECIFIC]
Допустим есть заголовок библиотеки:
void lib_init();
void lib_f1();
void lib_f2();
void lib_deinit();
Мы хотим, что бы нельзя было:
1. вызывать lib_f1/lib_f2, если не вызвали lib_init
2. вызывать lib_deinit, если не вызвали lib_f1/lib_f2 и lib_init
3. не вызвать lib_deinit, если вызывали lib_f1/lib_f2/lib_init
4. вообще ничего не вызывать из заголовка (иначе зачем включали?)
Сразу пример реализации этих требований, что бы не было скучно. С использованием неких вспомогательных классов (которые приведу ниже) эти требования можно записать следующим образом:
lib_ind<0> lib_init();
lib_ind<2, 0> lib_f1();
lib_ind<2, 0> lib_f2();
lib_ind<1, 0, 2> lib_deinit();
static final_check<1> lib_use_check_;
Где первый параметр lib_ind — это номер группы, к которой относится функция. В данном случае имеем три группы: группа 0 — функция lib_init, группа 1 — функция деинициализации lib_deinit, группа 2 — "рабочие" функции библиотеки lib_f1/lib_f2.
Последующие параметры lib_ind — номера групп, из которых надо вызвать хотя бы одну функцию перед функциями из данной группы.
С помощью final_check можно задать группы, из которых безусловно должна быть вызвана хотя бы одна функция. В данном случае задаём, что обязательно должна быть вызвана функция lib_deinit (группа 1).
Т.е. таким образом задаём граф корректной последовательности вызовов функций.
Данное описание задаёт следующий порядок вызовов функций:
int main()
{
lib_init();
lib_f1();
lib_f2();
lib_deinit();
}
Любые отклонения от данного порядка не скомпилируются! Если не верите — проверьте, это действительно так.
Далее код вспомогательных классов:
template<int id> struct lib_use
{
typedef int fake;
};
static lib_use<-1> lib_use_;
template<int id, int check1, int check2> struct lib_helper
{
typedef typename lib_use<id> fake;
#ifdef _MSC_VER
__if_not_exists(lib_use<check1>)
{
BOOST_STATIC_ASSERT(false);
}
__if_not_exists(lib_use<check2>)
{
BOOST_STATIC_ASSERT(false);
}
#endif
};
template<int id, int check1 = -1, int check2 = -1> class lib_ind
{
typedef typename lib_helper<id, check1, check2>::fake fake;
};
template<int id> struct final_check
{
final_check()
{
#ifdef _MSC_VER
__if_not_exists(lib_use<id>)
{
BOOST_STATIC_ASSERT(false);
}
#endif
}
};
Далее некоторые замечания:
Задаётся порядок именно упоминаний функций в коде (порядок вызовов в райн-тайм проверить достаточно тривиально). Следовательно скомпилируется следующий (некорректный) код:
void init()
{
lib_init();
}
void f()
{
lib_f1();
}
void main2()
{
f();
init();
lib_deinit();
}
Ну тут мы делаем по крайне мере не хуже чем было.
И в тоже время не скомпилируется следующий (корректный) код:
void f()
{
lib_f1();
}
void init()
{
lib_init();
}
void main2()
{
init();
f();
lib_deinit();
}
И естественно ничего не будет работать, если, например, инициализацию/деинициализацию вызывать в одной единице трансляции, а использовать библиотеку в другой, т.к. все эти проверки работают на уровне одной единицы трансляции.
О практической применимости такой техники в реальной жизни решайте сами.
В защиту могу сказать следующее. Такая техника заставляет:
1. Локализовывать использование библиотеки в одном cpp-файле. Размазывание вызовов по нескольким файлам в общем случае, я бы считал Плохой Вещью.
2. Форсируется использование библиотеки в виде некого заранее известного, корректного и локального паттерна кода. Например так:
int main()
{
lib_init();
lib_f1();
lib_f2();
lib_deinit();
}
Понятно, что можно и корректно использовать библиотеку, если разнести вызовы по разным функциям и перемешать их в хаотическом порядке. Но зачем? Если можно записать их в одной функции и в известном порядке. Т.о. если открыть файл, который видишь первый раз, человека, код которого ни разу не видел, всё равно будешь знать что ожидать.