Не секрет, что 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), либо использовать явное инстанцирование шаблонов для текущей платформы. Это не слишком сложно, скорее муторно и на самом деле редко нужно.
Что думает уважаемое С++-сообщество по этому поводу?