Подтвердить что ты не робот

Кросс-платформенный ООП в С++

Конечно, я знаю, что лучший ответ - "не пишите свой собственный кросс-платформенный код, кто-то уже сделал то, что вам нужно", но я делаю это как упражнение для занятий хобби/обучения, а не в любом платном вместимость. В основном, я пишу небольшое консольное приложение на С++, и я хотел бы сделать его кросс-платформой, имея дело с такими вещами, как файлы, сокеты и потоки. ООП - это отличный способ справиться с этим, но я не нашел хорошего шаблона для написания классов, которые используют одну и ту же межплатформенную платформу.

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

Я просмотрел некоторые из меньших классов в источнике wxWidgets, и они используют подход, который использует закрытый элемент, содержащий данные для класса, например

class Foo
{
    public:
        Foo();

        void Bar();
    private:
        FooData data;
};

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

Другим подходом, который я рассмотрел, является создание интерфейса и замена классов, которые наследуются от этого интерфейса в зависимости от платформы. Что-то вроде этого:

class Foo
{
    public:
        virtual ~Foo() {};
        virtual void Bar() = 0;
};

class Win32Foo
{
    public:
        Win32Foo();
        ~Win32Foo();
        void Bar();
};

Конечно, этот вид винтов вверх по фактическому экземпляру, так как вы не знаете, какую реализацию для создания объекта, но это можно обойти с помощью функции

Foo* CreateFoo();

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

Какой из этих двух подходов лучше? Есть ли лучший способ?

Изменить: Чтобы уточнить, мой вопрос не "Как вы пишете кросс-платформенный С++?" Скорее, это "Какой лучший способ абстрагировать кросс-платформенный код с помощью классов на С++, сохраняя при этом как можно больше преимуществ от системы типов?"

4b9b3361

Ответ 1

Определите свой интерфейс, который пересылает вызовы detail:

#include "detail/foo.hpp"

struct foo
{
    void some_thing(void)
    {
        detail::some_thing();
    }
}

Где "detail/foo.hpp" выглядит примерно так:

namespace detail
{
    void some_thing(void);
}

Затем вы реализуете это в detail/win32/foo.cpp или detail/posix/foo.cpp, и в зависимости от какой платформы вы компилируете, компилируете тот или другой.

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

Ответ 2

Чтобы реализовать кросс-платформенные функции, требующие поддержки ОС (например, сокеты), вам придется писать код, который просто не будет компилироваться на определенных платформах. Это означает, что ваш объектно-ориентированный дизайн должен быть дополнен препроцессорными директивами.

Но так как вам все равно придется использовать препроцессор, то сомневается, что что-то вроде win32socket (например), которое наследуется от базового класса socket, даже необходимо или полезно. OO обычно полезен, когда различные функции будут полиморфно выбраны во время выполнения. Но межплатформенная функциональность обычно больше связана с временем компиляции. (например, нет использования в полиморфном вызове win32socket::connect, если эта функция даже не компилируется в Linux!) Поэтому было бы лучше просто создать класс socket, который по-разному реализуется в зависимости от платформы, используя директивы препроцессора.

Ответ 3

Вам не нужно идти ООП, чтобы быть кросс-платформой. Кросс-платформа - это скорее парадигма архитектуры и дизайна.

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

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

Ответ 4

В идеале вы могли бы сконцентрировать различия на самом низком уровне.
Таким образом, ваш код верхнего уровня вызывает Foo(), и только Foo() должен заботиться о том, какую ОС он вызывает.

пс. Взгляните на "boost", он содержит много материала для обработки файловых систем с межплатформенной сетью и т.д.

Ответ 5

Вы можете использовать специализированную специализацию для разделения кода для разных платформ.

enum platform {mac, lin, w32};

template<platform p = mac>
struct MyClass
{
 // Platform dependent stuff for whatever platform you want to build for default here...
};

template<>
struct MyClass<lin>
{
 // Platform dependent stuff for Linux...
};

template<>
struct MyClass<w32>
{
 // Platform dependent stuff for Win32...
};

int main (void)
{
 MyClass<> formac;
 MyClass<lin> forlin
 MyClass<w32> forw32;
 return 0;
}

Ответ 6

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

Пример:

class Foo
{
    public:
        virtual ~Foo() {};
        virtual void Bar() = 0;
};   
class Win32Foo : public Foo
{
    public:
        void Bar();
        void CloseWindow(); // Win32-specific function
};

class Abc
{
    public:
        virtual ~Abc() {};
        virtual void Xyz() = 0;
};   
class Win32Abc : public Abc
{
    public:
        void Xyz()
        {
           // do something
           // and Close the window
           Win32Foo foo;
           foo.CloseWindow();
        }
};

Ответ 7

Делегирование как в вашем первом примере, так и в GMan работает очень хорошо, особенно если для конкретной платформы потребуется множество определенных для платформы членов (например, членов типов, которые существуют только на одной платформе). Недостатком является то, что вам нужно поддерживать два класса.

Если класс будет проще, без множества специфичных для платформы членов, вы можете просто использовать общее объявление класса и написать две реализации в двух разных файлах .cc(плюс, возможно, третий .cc для независимого от платформы метода реализации). Вам могут понадобиться некоторые #ifdefs в файле заголовка для нескольких членов или членов платформы, имеющих типы платформы. Этот способ - скорее взлом, но с ним легче начать. Вы всегда можете переключиться на делегирование, если оно выходит из-под контроля.

Ответ 8

Как отмечали другие, наследование - неправильный инструмент для работы. Решение конкретного типа для использования в вашем случае выполняется во время сборки, и классический полиморфизм позволяет принять это решение во время выполнения (т.е. В результате действия пользователей).