Не секрет, что 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), либо использовать явное инстанцирование шаблонов для текущей платформы. Это не слишком сложно, скорее муторно и на самом деле редко нужно.
Что думает уважаемое С++-сообщество по этому поводу?
[skip]
O>У нас возникла идея альтернативного подхода к этому вопросу, а именно использование traits. Сущность идеи, конечно, не нова, но я ни разу не видел использования этого в наличествующих библиотеках именно для работы с платформами.
O>Итак, немного [псевдо]кода: O>
O>typedef enum
O>{
O> win32, win32_ce, linux, /* другие платформы */
O>#if defined(_WIN32)
O> #define PLATFORM_WIN32
O> platform = win32
O>#elif defined(_WIN32_WCE)
O> #define PLATFORM_WIN32_CE
O> platform = win32_ce
O>#elif/* другие платформы */
O>#else
O> #error defaults not correct; you must hand modify platform_def.h
O>#endif
O>} platform_type;
O>
ИМХО, ты на верном пути. Только мне кажется, что лучше идентификаторы платформ поместить не в enum, а сделать их в виде Dummy-классов.
Попробуй сделать так:
class PlatformWin32; // Всё. У этого класса никакого тела не будет.class PlatformLinux; // У этого - тоже.#ifdef PLATFORM_WIN32
typedef PlatformWin32 CurrentPlatform;
#else
...
#endif// Определения шаблонов платформозависимых классов:template<typename PLATFORM, typename Tr = type_trait<PLATFORM> >
class mutex_trait { ... };
// Специализацияtemplate<>
class mutex_trait<PlatformLinux> { ... };
template<>
class mutex<PlatformLinux> { ... }
// Использование в платформонезависимом коде:
mutex<CurrentPlatform> some_mutex;
// И т.д.
Специализации можно раскидать по отдельным инклюдникам как и в предлагаемом тобой варианте.
[skip]
O>Что думает уважаемое С++-сообщество по этому поводу?
"Я вам не скжу за всю Одессу"
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте orangy, Вы писали:
O>Не секрет, что CreateFile — вещь сугубо внидовзная А еще бывает много другого платформенно-зависимого кода. Исторически сложилось так, что такой код обрамляют O>
O>#if defined(PLATFORM_WIN32)
O>...
O>#endif
O>
<...> O>У нас возникла идея альтернативного подхода к этому вопросу, а именно использование traits.
Одно время я тоже был склонен к подобным изыскам, но в последствии остановился на более простом решении:
Здравствуйте Геннадий Васильев, Вы писали:
ГВ>ИМХО, ты на верном пути.
Верность пути определяется прибытием в верный пункт назначения
ГВ>Только мне кажется, что лучше идентификаторы платформ поместить не в enum, а сделать их в виде Dummy-классов. ГВ>[skip]
Мысль интересная:
+ расширяется (можно добавлять собственные платформы в клиентском коде)
Хотя это может быть не всегда хорошо. Еще можно поместить такие объявления в отдельный namespace.
— не контролируется — в качестве параметра можно подсунуть всё, что угодно в том числе и клиентский класс.
mutex<myclass> может конечно означать "специальный мутех для моего класса", но в целом кажется опасным..
enum же закрыт, так что ничего неожиданного случиться не должно
Здравствуйте orangy, Вы писали:
O>Не секрет, что CreateFile — вещь сугубо внидовзная А еще бывает много другого платформенно-зависимого кода. Исторически сложилось так, что такой код обрамляют O>
O>#if defined(PLATFORM_WIN32)
O>...
O>#endif
O>
O>или чем-то подобным. Отрицательные стороны такого подхода очевидны: O>1. замусореный код если требуется поддерживать больше одной платформы O>2. неудобство совместной разработки (когда один программист разрабатывает код для одной платформы, другой — для другой) O>3. плохая расширяемость (для добавления новой платформы нужно перелопатить практически весь зависимый код) O>и т.п.
В смысле? Ты хочешь сказать, что подобное делается прямо в каждом месте где надо вызвать платформенно зависимую функцию? Нет, так не делается. Разрабатывается некотрый платформенно-независимый интерфейс, который реализуется через платформенно зависимые вызовы. Последние выбираются при помощи препроцессора или путем подстановки правильных реализаций (через исходные файлы или библиотеки) на уровне make-файла. Этот подход не обладает недостатками 1 и 2. А недостаток 3 присутствует и будет присутствовать всегда и во всех подходах. Непосредственно зависимый от платформы код придется писать инидивидуально для каждой платформы. От этого никуда не денешься.
Ты предлагаешь к методам "препроцессор" и "make-файл" добавить еще третий альтернативный метод — "шаблоны". Этот третий метод не обладает никакими преимуществами перед первыми двумя, кроме того что можно, как ты уже указал, смешивать разноплатформенные вызовы внутри одного проекта (это, вообще-то, весьма специфическая функциональность). Также стоит помнить то, что компилятор при компиляции определения шаблона имеет полное право сразу попытаться проверить факт объявленности независимых имен. Т.е. виндосовские traits с вызовом, например, '::EnterCriticalSection' имеют полное право не скомпилироваться под Linux (даже если эти traits нигде явно не используются в программе). Чтобы решить эту проблему придется пользоваться все тем же препроцессором или управлять копиляцией на уровне make-файла, что опять таки приводит нас к первым двум методам.
Сразу оговорюсь — я не спорю ради спора, я пытаюсь понять как лучше сделать
АТ>В смысле? Ты хочешь сказать, что подобное делается прямо в каждом месте где надо вызвать платформенно зависимую функцию? Нет, так не делается.
Я не хочу сказать, что я так делаю, но, увы, так делается... Видел множество безобразного кода, в котором зачастую невозможно разобраться. Пример из STL от SGI (stl_alloc.h)
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
_NFREELISTS
# else
__default_alloc_template<__threads, __inst>::_NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
// The 16 zeros are necessary to make version 4.1 of the SunPro
// compiler happy. Otherwise it appears to allocate too little
// space for the array.
Прямо посередине файла.
AT>Разрабатывается некотрый платформенно-независимый интерфейс, который реализуется через платформенно зависимые вызовы.
Интерфейс какой? Чистая абстракция? Виртуальные функции? Описание контракта в комментариях?
AT>Последние выбираются при помощи препроцессора или путем подстановки правильных реализаций (через исходные файлы или библиотеки) на уровне make-файла.
Имеется ввиду определение include path при сборке для данное платформы?
AT>Этот подход не обладает недостатками 1 и 2. А недостаток 3 присутствует и будет присутствовать всегда и во всех подходах.
Наверное я невнятно выразился... Я имел ввиду, что при "традиционном" подходе (а-ля приведенному выше), придётся перелопатить весь код, потенциально зависимый от платформы, в поисках/исправлениях/портировании...
AT>Непосредственно зависимый от платформы код придется писать инидивидуально для каждой платформы. От этого никуда не денешься.
Это бесспорно.
АТ>Ты предлагаешь к методам "препроцессор" и "make-файл" добавить еще третий альтернативный метод — "шаблоны".
Я не предлагаю добавить, я рассматриваю варианты. Если бы я был твёрдо уверен в таком подходе — я бы не спрашивал в RSDN мнения, а сразу бы так написал. Спасибо за комментарии.
АТ>Также стоит помнить то, что компилятор при компиляции определения шаблона имеет полное право сразу попытаться проверить факт объявленности независимых имен.
А вот здесь, плиз, поподробнее. Это есть в стандарте или это практическое наблюдение?
Кстати, еще вопрос по теме (навеяно ответом Геннадия Васильева)
Вводит ли typename тип? Что-то я в стандарте на эту тему не нашёл...
например:
namespace platforms
{
typename win32;
typename linux;
}
template<typename T>
class some_traits
{};
template<>
class some_traits<platforms::win32>
{...};