Платформенно-зависимый код и С++
От: 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!"
Re: Платформенно-зависимый код и С++
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 14.09.02 22:32
Оценка:
Здравствуйте orangy, Вы писали:

[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.: Винодельческие провинции — это есть рулез!
Re: Платформенно-зависимый код и С++
От: Павел Кузнецов  
Дата: 15.09.02 08:07
Оценка:
Здравствуйте orangy, Вы писали:

O>Не секрет, что CreateFile — вещь сугубо внидовзная А еще бывает много другого платформенно-зависимого кода. Исторически сложилось так, что такой код обрамляют

O>
O>#if defined(PLATFORM_WIN32)
O>...
O>#endif
O>

<...>
O>У нас возникла идея альтернативного подхода к этому вопросу, а именно использование traits.

Одно время я тоже был склонен к подобным изыскам, но в последствии остановился на более простом решении:

// w32/File.h
namespace w32 {
class File { . . . };
}
// posix/File.h
namespace posix {
class File { . . . };
}
// <Whatever>File.h

. . .

// File.h
#if defined PLATFORM_WIN32
#include "w32/File.h"
typedef w32::File File;
#elif defined PLATFORM_POSIX
#include "posix/File.h"
typedef posix::File File;
#elif . . .
. . .
#endif
<< J 1.0 alpha 4 >>
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: Платформенно-зависимый код и С++
От: Павел Кузнецов  
Дата: 15.09.02 08:14
Оценка:
Еще вспомнилось. Кто-то (по-моему, Pete Becker) предлагал делать так:

// File.h
#if defined PLATFORM_WIN32
#include "w32/File.h"
namespace platform = w32;
#elif defined PLATFORM_POSIX
#include "posix/File.h"
namespace platform = posix;
#elif . . .
. . .
#endif
typedef platform::File File;
<< J 1.0 alpha 4 >>
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: Платформенно-зависимый код и С++
От: orangy Россия
Дата: 16.09.02 07:50
Оценка:
Здравствуйте Геннадий Васильев, Вы писали:

ГВ>ИМХО, ты на верном пути.

Верность пути определяется прибытием в верный пункт назначения

ГВ>Только мне кажется, что лучше идентификаторы платформ поместить не в enum, а сделать их в виде Dummy-классов.

ГВ>[skip]
Мысль интересная:
+ расширяется (можно добавлять собственные платформы в клиентском коде)
Хотя это может быть не всегда хорошо. Еще можно поместить такие объявления в отдельный namespace.
— не контролируется — в качестве параметра можно подсунуть всё, что угодно в том числе и клиентский класс.
mutex<myclass> может конечно означать "специальный мутех для моего класса", но в целом кажется опасным..
enum же закрыт, так что ничего неожиданного случиться не должно
... << J 1.0 alpha 4 >>
"Develop with pleasure!"
Re: Платформенно-зависимый код и С++
От: Андрей Тарасевич Беларусь  
Дата: 16.09.02 08:26
Оценка:
Здравствуйте 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-файла, что опять таки приводит нас к первым двум методам.
Best regards,
Андрей Тарасевич
Re[2]: Платформенно-зависимый код и С++
От: orangy Россия
Дата: 16.09.02 09:21
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

Сразу оговорюсь — я не спорю ради спора, я пытаюсь понять как лучше сделать

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

Я не хочу сказать, что я так делаю, но, увы, так делается... Видел множество безобразного кода, в котором зачастую невозможно разобраться. Пример из 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>
{...};
... << J 1.0 alpha 4 >>
"Develop with pleasure!"
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.