LINUX.ORG.RU

Условная компиляция кросплатформенного класса и его поля

 


0

3

Допустим, у меня есть некий класс Process, позволяющий запускать исполняемый файл с аргументами, приделывать файл на ввод-вывод и ждать завершения. Интерфейс по возможности платформонезависимый, т.е. всякие там std::filesystem вместо строк для путей и т.д.

Думаю, что будет process.h с интерфейсом и что-то типа process_unix.cpp и process_win.cpp, которые приделываются CMake-ом в зависимости от платформы. Вопрос: куда пихать приватные платформозависимые поля, типа PID в Unix?

Пока в голову приходит только void* на структуру с полями, определённую в cpp-шнике, и каст к указателю на структуру в нём же, но выглядит некрасиво. Есть решения получше?


#if defined(BXI_OS_GLX)
    i32 pid = fork();
    if (pid == 0) {
        execve(program, args, environ);
        exit(EXIT_SUCCESS);
    } else {
        i32 status;
        i32 wpid;
        while ((wpid = wait(&status)) > 0) {
            if (status != 0)
                return status;
        }
    }
    return 0;

#elif defined(BXI_OS_WIN)
    LPWSTR wprog = _frs_stringa2w(program);
    LPWSTR wargs = _frs_argsa2w  (args);

    PROCESS_INFORMATION pi = { 0 };
    STARTUPINFO         si = { 0 };

    if (CreateProcess(wprog, wargs, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    } else {
        bxi_free(wprog);
        bxi_free(wargs);
        return FRS_EXIT_RAW_CANNOT_EXECUTE;
    }

    bxi_free(wprog);
    bxi_free(wargs);
    return 0;
#endif

У нас пришлось сделать так.

PPP328 ★★★ ()

Сделать абстрактный класс Process, платформозависимая реализация наследует её. А поток создаёт статическая функция абстрактного класса, реализация которой также платформозависима и использует уже правильный класс.

Либо совать в класс хотя бы не void, а указатель на forward declarated struct, конкретный набор полей для которой определяется уже в файле реализации.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 1)

Сделай приватный класс, в котором будешь держать детали реализации по классу на каждую платформу, а этот оставь интерфейсом

XMs ★★★★★ ()
Ответ на: комментарий от Ower

Зойчем ему пимпл, он же не в динамике будет выбирать реализацию для своего класса :) Пусть юзает CRTP, как большие дяди, разууж все равно хочет каст :)

slackwarrior ★★★★★ ()
Последнее исправление: slackwarrior (всего исправлений: 1)

Вариант №1 с идиомой PImpl:

// process.hpp

class process {
   struct impl;
   std::unique_ptr<impl> impl_;
public:
   ...
};

// process.cpp
#include "process.hpp"

#if defined(__unix__)
#include <unistd.h>
...
struct process::impl {
   ...
};

#elif defined(__WIN32__)
#include <windows.h>
...
struct process::impl {
   ...
};
#endif

process::process(...) : impl_{new impl{...}} {...}

Вариант №2, с абстрактным классом и фабрикой:

// process.hpp

class process {
public:
   virtual ~process();
   ...
};

std::unique_ptr<process> make_process();

// process.cpp
#include "process.hpp"

#if defined(__unix__)
#include <unistd.h>
...
class unix_process : public process {
   ...
};

std::unique_ptr<process> make_process() {
   return std::make_unique<unix_process>(...);
}

#elif defined(__WIN32__)
#include <windows.h>
...
class win_process : public process {
   ...
};

std::unique_ptr<process> make_process() {
   return std::make_unique<win_process>(...);
}

#endif

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

eao197 ★★★★★ ()

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

tsmx ()
Ответ на: комментарий от tsmx

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

Cmake тут причем? Это просто фронтенд, что генерит входные файлы для систем сборки типа gnu-make или ninja. он сам ничего не собирает.

даже #include - директива препроцессора, от препроцессора в рамках си или плюсов избавиться невозможно.

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

alysnix ()

Допустим, у меня есть некий класс Process, позволяющий запускать исполняемый файл с аргументами, приделывать файл на ввод-вывод и ждать завершения. Интерфейс по возможности платформонезависимый, т.е. всякие там std::filesystem вместо строк для путей и т.д.

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

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

alysnix ()
Ответ на: комментарий от XMs

Только немного не так. ifdef, по возможности, стоит избегать - ухудшает читабельность, усложняет поддержку. Просто struct Impl и отдельный cpp для каждой платформы.

Ower ()

Пока в голову приходит только void*

void* в современном С++ это code smell. Его как минимум везде можно заменить на std::any. Профитом будет то, что некорректные касты будут кидать исключения вместо UB.

fsb4000 ★★★ ()

Пока в голову приходит только void* на структуру с полями, определённую в cpp-шнике, и каст к указателю на структуру в нём же, но выглядит некрасиво. Есть решения получше?

если свербит, и не хочется что-то на куче аллокировать, аллокируй прям в обьекте. переопределив new/delete, для скрытого класса, или пользуясь in-place конструированием. а в обьекте будут байтовый буфер под аллокирование, плюс указатель на скрытый класс, чей экземпляр аллокирован прямо в открытом классе.

alysnix ()

Пока в голову приходит только void* на структуру с полями, определённую в cpp-шнике, и каст к указателю на структуру в нём же, но выглядит некрасиво. Есть решения получше?

Указатель на класс/структуру с forward declaration (PIMPL)

Вопрос: куда пихать приватные платформозависимые поля, типа PID в Unix?

Не самое платформозависимое поле, так как не требует включения платформенных заголовков - такое и под ifdef можно засунуть

annulen ★★★★★ ()

Зачем переизобретать то что уже давно сделано стопицот раз?

Это во-первых. Во-вторых, кроссплатформенный софт как правило нахрен не нужен, особенно десктопный.

lovesan ★☆ ()

void* никогда не нужно использовать. Варианты:

  • виртуальный класс + наследованные платформоспецифичные реализации
  • обычный класс + pImpl + платформоспецифичные имплементации
slovazap ★★★★★ ()
Ответ на: комментарий от Ower

Еще как уместен, см. например CRTP-based platform-dependent optimizations, это все, вместе с приватными хедерами еще и «опциями системы сборки», плюс-минус эквичлено с точки зрения сорцовой кроссплатформенности — можно хоть sedом и awkом нужные сорцы собирать :) Даже макросам эквичленно, но перед ними у современных плюсистов есть некоторое предубеждение :)

И все это сорта размежевания имплементаций, только pimpl динамический со своими издержками и какбэ выгодой для сокращения времени конпеляцыи, другой, CRTP — с ликвидацией динамических задрочек в рантайме, если «очень не важно» на время конпеляции (а в имплементации эмбед приложений, как в GNSS или шаблонизированных интерпретаторов протоколов (выражений «ключ->значение») для прошивок под асики/плисы, например, на это «очень не важно», важнее как оно щелкает в прошивке, а не сколько конпеляется — если на core2duo с 4мя Гигами можно было и день подождать, пока все спецификации машинно-генереных шаблонов протокольной библиотеки обработаются, то сейчас это от силы пару часов занимает).

Конечно, не ленивые ответственные люди просто не будут заниматься ритуальной фигней с сомнительными преимуществами для поддержки штанов менеджерам и лидам «бестпрактиками», набирающим по объявлению «чтоб дешевле» и т.д. на основе знаний модных буззвордов: «сопровождабельности», «современных аджайл практик», «коде гайдлайнов», «паттернов» и прочего квадратно-гнездового программирования для структурирования мозга девелоперам с запретом использования всего что «может быть источником проблем, если про это узнают пионЭры» вида «а вдруг они в ногу себе выстрелят», «зачем им бензопила — порежутся еще»...

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

slackwarrior ★★★★★ ()
Последнее исправление: slackwarrior (всего исправлений: 2)