Кроссплатформенная архитектура кода C ++ - PullRequest
15 голосов
/ 10 марта 2010

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

На мой взгляд, возможны два основных подхода: независимые от платформы классы с конкретными для платформы делегатами или независимые от платформы классы с определенными для платформы производными классами. Есть ли присущие преимущества / недостатки любого из этих подходов? И в любом случае, каков наилучший механизм для установки отношения делегирования / наследования, чтобы процесс был прозрачным для пользователя независимых от платформы классов?

Буду признателен за любые предложения относительно аккуратной архитектуры для использования или даже за некоторые примеры того, что люди делали в прошлом, и за / против данного подхода.

РЕДАКТИРОВАТЬ: в ответ на те, кто предлагает Qt и тому подобное, да, я намеренно стремлюсь «заново изобрести колесо», так как я не просто заинтересован в разработке приложения, я также заинтересован в интеллектуальном вызове - собственная библиотека абстракций платформы. Спасибо за предложение, хотя!

Ответы [ 8 ]

12 голосов
/ 10 марта 2010

Я использую нейтральные для платформы заголовочные файлы, сохраняя любой исходный код платформы в исходных файлах (используя PIMPL идиома , где это необходимо). Каждый нейтральный заголовок платформы имеет один исходный файл для каждой платформы с такими расширениями, как *.win32.cpp, *.posix.cpp. Специфичные для платформы скомпилированы только на соответствующих платформах.

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

Это независимые от платформы объявления классов с определениями, специфичными для платформы.

Плюсы: Работает довольно хорошо, не зависит от препроцессора - нет #ifdef MyPlatform, позволяет легко идентифицировать код конкретной платформы, позволяет использовать специфические функции компилятора в исходных файлах конкретной платформы, не загрязняет глобальное пространство имен #include заголовками платформы.

Минусы: Трудно использовать наследование с классами pimpled, иногда структурам PIMPL требуются свои собственные заголовки, чтобы на них можно было ссылаться из исходных файлов других платформ.

4 голосов
/ 10 марта 2010

Другой способ - иметь независимые от платформы соглашения, но заменять исходный код, специфичный для платформы, во время компиляции.

То есть, если вы представляете компонент Foo, он должен быть привязан к платформе (например, сокеты или элементы графического интерфейса), но иметь следующие открытые члены:

class Foo {
public:
  void write(const char* str);
  void close();
};

Каждый модуль, который должен использовать Foo, очевидно, имеет #include "Foo.h", но в файле make для конкретной платформы у вас может быть -IWin32, что означает, что компилятор ищет в .\Win32 и находит специфическую для Windows Foo.h, который содержит класс, с тем же интерфейсом, но, возможно, специфичные для Windows частные члены и т. Д.

Таким образом, не существует ни одного файла, который содержит Foo, как написано выше, а только наборы файлов, специфичных для платформы, которые используются только при выборе файлом make для конкретной платформы.

3 голосов
/ 10 марта 2010

Посмотрите на ACE . У него довольно хорошая абстракция с использованием шаблонов и наследования.

2 голосов
/ 10 марта 2010

Я мог бы пойти на вещь типа политики:

template<typename Platform>
struct PlatDetails : private Platform {
    std::string getDetails() const {
        return std::string("MyAbstraction v1.0; ") + getName();
    }
};

// For any serious compatibility functions, these would
// of course have to be in different headers, and the implementations
// would call some platform-specific functions to get precise
// version numbers. Using PImpl would be a smart idea for these 
// classes if they need any platform-specific members, since as 
// Joe Gauterin says, you want to avoid your application code indirectly
// including POSIX or Windows system headers, containing useless definitions.
struct Windows {
    std::string getName() const { return "Windows"; }
};

struct Linux {
    std::string getName() const { return "Linux"; }
};

#ifdef WIN32
    typedef PlatDetails<Windows> PlatformDetails;
#else
    typedef PlatDetails<Linux> PlatformDetails;
#endif

int main() {
    std::cout << PlatformDetails().getName() << "\n";
}

Не так уж и много выбора между выполнением этого и регулярным симулированным динамическим связыванием с CRTP, так что общая вещь является базовой, а конкретная вещь - производным классом:

template<typename Platform>
struct PlatDetails {
    std::string getDetails() const {
        return std::string("MyAbstraction v1.0; ") + 
            static_cast<Platform*>(this)->getName();
    }
};

struct Windows : PlatDetails<Windows> {
    std::string getName() const { return "Windows"; }
};

struct Linux : PlatDetails<Linux> {
    std::string getName() const { return "Linux"; }
};

#ifdef WIN32
    typedef Windows PlatformDetails;
#else
    typedef Linux PlatformDetails;
#endif

int main() {
    std::cout << PlatformDetails().getName() << "\n";
}

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

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

На практике, однако, я согласен с Quamrana, что обычно у вас могут быть разные реализации одной и той же вещи на разных платформах:

// Or just set the include path with -I or whatever
#ifdef WIN32
    #include "windows/platform.h"
#else
    #include "linux/platform.h"
#endif

struct PlatformDetails {
    std::string getDetails() const {
        return std::string("MyAbstraction v1.0; ") + 
            porting::getName();
    }
};

// windows/platform.h
namespace porting {
    std::string getName() { return "Windows"; }
}

// linux/platform.h
namespace porting {
    std::string getName() { return "Linux"; }
}
0 голосов
/ 10 марта 2010

Есть также большие парни, такие как Qt4 (полный фреймворк + GUI), GTK + (только графический интерфейс) и Boost ( только фреймворк, без графического интерфейса), все 3 поддерживают большинство платформ, GTK + - это C, Qt4 / Boost - это C ++ и по большей части основано на шаблонах.

0 голосов
/ 10 марта 2010

Итак ... вы не хотите просто использовать Qt? Для реальной работы с использованием C ++ я очень рекомендую это. Это абсолютно отличный кроссплатформенный инструментарий. Я только что написал несколько плагинов, чтобы заставить его работать на Kindle, а теперь и на Palm Pre. Qt делает все легко и весело. Прямо омолаживающий, даже. Ну, до твоего первого знакомства с QModelIndex, но они предположительно поняли, что перепроектировали его, и они заменяют его;)

Как академическое упражнение, это интересная проблема. Как сам изобретатель колеса, я даже делал это несколько раз. :)

Краткий ответ: я бы пошел с PIMPL. (В источниках Qt много примеров)

В прошлом я использовал базовые классы и производные классы, специфичные для платформы, но обычно это оказывается немного более грязным, чем я думал. Я также сделал часть реализации с использованием некоторой степени указателей на функции для специфичных для платформы битов, и я был еще менее доволен этим.

Оба раза у меня возникало очень сильное чувство, что я чрезмерно разбираюсь в архитектуре и заблудился.

Я обнаружил, что использование частных классов реализации (PIMPL) с битами, специфичными для разных платформ, в разных файлах проще всего написать AND debug. Однако ... не пугайтесь # ifdef или двух, если это всего лишь несколько строк и очень ясно, что происходит. Я ненавижу загроможденную или вложенную логику # ifdef , но одна или две здесь и там могут действительно помочь избежать дублирования кода.

С PIMPL вы не будете постоянно реорганизовывать свой дизайн, поскольку обнаруживаете новые элементы, которые требуют разных реализаций между платформами. Так будут драконами.

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

Во всяком случае, самая большая неприятность, которую я испытываю по поводу кодирования сегодня, это то, как легко люди, кажется, теряются в идеализме. PIMPL - это шаблон, а платформенные производные классы - это еще один шаблон. Использование указателей на функции - это шаблон. Там нет ничего, что говорит, что они взаимоисключающие.

Однако, как общее руководство ... начните с PIMPL.

0 голосов
/ 10 марта 2010

Если вы хотите использовать полноценный фреймворк c ++, доступный для многих платформ с разрешающим копированием, используйте Qt.

0 голосов
/ 10 марта 2010

Вы также можете взглянуть на poco :

Библиотеки POCO C ++ (POCO расшифровываются как POrtable COmponents) - это библиотеки классов C ++ с открытым исходным кодом, которые упрощают и ускоряют разработку ориентированных на сеть портативных приложений на C ++. Библиотеки прекрасно интегрируются со стандартной библиотекой C ++ и заполняют многие из оставленных ею функциональных пробелов. Их модульная и эффективная конструкция и реализация делают библиотеки POCO C ++ чрезвычайно подходящими для разработки встраиваемых систем - области, где язык программирования C ++ становится все более популярным благодаря его пригодности как для низкоуровневого (ввод-вывод устройства, обработчики прерываний и т. Д. .) и высокоуровневая объектно-ориентированная разработка. Разумеется, библиотеки POCO C ++ также готовы к решению задач корпоративного уровня.

poco architecture
(источник: pocoproject.org )

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...