Кросс-платформенная архитектура кода C++

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

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

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

Править: в ответ на тех, которые предлагают QT и подобный, да, я намеренно надеюсь "изобретать велосипед", поскольку я только обеспокоен разработкой приложения, я также интересуюсь интеллектуальной проблемой прокрутки моей собственной библиотеки абстракции платформы. Спасибо за предложение, хотя!

15
задан Mac 10 March 2010 в 01:36
поделиться

8 ответов

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

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

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

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

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

12
ответ дан 1 December 2019 в 03:34
поделиться

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

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

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

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

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

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

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

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

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

Однако в качестве общего правила ... начните с PIMPL.

0
ответ дан 1 December 2019 в 03:34
поделиться

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

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

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

0
ответ дан 1 December 2019 в 03:34
поделиться

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

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

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

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

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

4
ответ дан 1 December 2019 в 03:34
поделиться

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

3
ответ дан 1 December 2019 в 03:34
поделиться

Я мог бы использовать политику типа:

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"; }
}
2
ответ дан 1 December 2019 в 03:34
поделиться

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

0
ответ дан 1 December 2019 в 03:34
поделиться

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

0
ответ дан 1 December 2019 в 03:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: