Платформенно-зависимый код и С++
От: orangy Россия
Дата: 13.09.02 12:27
Оценка: 11 (2)
Не секрет, что CreateFile — вещь сугубо внидовзная А еще бывает много другого платформенно-зависимого кода. Исторически сложилось так, что такой код обрамляют
#if defined(PLATFORM_WIN32)
...
#endif

или чем-то подобным. Отрицательные стороны такого подхода очевидны:
1. замусореный код если требуется поддерживать больше одной платформы
2. неудобство совместной разработки (когда один программист разрабатывает код для одной платформы, другой — для другой)
3. плохая расширяемость (для добавления новой платформы нужно перелопатить практически весь зависимый код)
и т.п.
Есть и свои достоинства:
1. локальность зависимых вызовов, что позволяет поддерживать изменяющиеся реализации в синхронном состоянии
2. разгрузка компилятора (нагрузка перенесена на [потенциально] более быстрый препроцессор)
и т.п.

У нас возникла идея альтернативного подхода к этому вопросу, а именно использование traits. Сущность идеи, конечно, не нова, но я ни разу не видел использования этого в наличествующих библиотеках именно для работы с платформами.
Итак, немного [псевдо]кода:
typedef enum
{
    win32, win32_ce, linux, /* другие платформы */
#if defined(_WIN32)
    #define PLATFORM_WIN32
    platform = win32
#elif defined(_WIN32_WCE)
    #define PLATFORM_WIN32_CE
    platform = win32_ce
#elif /* другие платформы */
#else
    #error defaults not correct; you must hand modify platform_def.h
#endif
} platform_type;


Пока всё просто Далее, рассмотрим mutex:
template<platform_type plt>
struct mutex_traits 
{
/*
Define type for object handle
    typedef <<wait object handle type for system>> handle_type;
Implement these methods for you favorite system
    static void create(handle_type *handle, int param) 
    static void destroy(handle_type *handle)  
    static bool acquire(handle_type *handle)  
    static bool release(handle_type *handle)  
*/
};


Включаем трейтсы, используем препроцессор для расслабления компилятора:
#if defined(PLATFORM_WIN32)
    #include "mutex_traits.win32.h"
#else
    #error Mutex traits for current platform not defined
#endif

Теперь собственно сам шаблонный класс mutex

template<platform_type plt, typename Tr = mutex_traits<plt> >
class basic_mutex
{
    typedef Tr::handle_type handle_t;
protected:
    handle_t handle;
public:
    bool acquire()
    {
      return Tr::acquire(&handle);
    }

/* и т.п. с использванием трейтсов */
};
typedef basic_mutex<platform> mutex; // алиас для "текущей" платформы


Бонусы подобного подхода:
1. раздельный код для разных платформ, снимает проблему №1
2. код в разных файлах, снимает проблему №2
3. высокая расширяемость, для добавления платформы описать отдельно traits
4. проверка интерфейсов во время компиляции — класс-пользователь traits требует определенного интерфейса
5. встраивание платформенно-зависимого кода (в данном примере вызов mutex::acquire развернется для виндов просто в вызов WaitForSingleObject, а размер mutex<win32> точно равен размеру HANDLE)
6. использование нескольких платформ одновременно. Звучит странно, но как пример можно рассмотреть POSIX как платформу и использовать например file<win32>, file<posix>, thread<win32>, thread<posix>, etc

Недостатки:
1. изменение поведения должно быть синхронизовано для трейтсов. К этой проблеме есть простое решение — версионность на уровне специализации:
template<platform_type plt = platform, typename Tr = mutex_traits<plt,1> > // 1 - это версия интерфейса
class mutex;

При изменении интерфейса, клиент (в данном случае mutex) начинает требовать новую версию трейтсов, соответственно несогласованные версии трейтсов для разных платформ не будут компилятся и потребуют согласования. Правда, это уже некоторое внедрение организационных процессов в код и заслуживает отдельной дискуссии.

2. Раздувание кода. Этот приём очень хорош для таких вот классов-оболочек, однако для более сложных платформенно-зависимых сущностей это может привести к слишком большому (хотя возможно более быстрому, засчёт локальной оптимизации) коду.
Решение этой проблемы также возможно, хоть и не столь элегантно. Либо использовать export (увы, увы, пока только в comeau), либо использовать явное инстанцирование шаблонов для текущей платформы. Это не слишком сложно, скорее муторно и на самом деле редко нужно.

Что думает уважаемое С++-сообщество по этому поводу?
"Develop with pleasure!"
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.