[Trick] Форсирование правильного использования библиотеки
От: remark Россия http://www.1024cores.net/
Дата: 21.01.07 11:47
Оценка: 12 (3)
Идём ещё дальше, чем здесь
Автор: 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();
}

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



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.