Kilka wersji API dzięki kompilacji warunkowej

0

Czy w przypadku pisania biblioteki można użyć kompilacji warunkowej aby stworzyć "elastyczne" API dla różnych przypadków użycia?

Np. biblioteka będzie pisana w Qt ale musi być "developer friendly" dla tych którzy bedą chcieli jej użyć w projekcie nie używającym Qt więc funkcje zwracające tekst mają w środowisku Qt zwracać QString a w projekcie nie używającym Qt ma to być std::string.

Podobnie - czy można użyć kompilacji warunkowej do stworzenia "elastycznego" systemu obsługi błędów? Np podając pewną flage podczas kompilacji wybieramy czy chcemy API które używa wyąjtków czy chcemy API bez wyjątków a np z kodami błędów?

Czy ktoś z was już coś takiego robił?
Czy jest to możliwe?
Zalety, wady takiego rozwiązania w przypadku pisania biblioteki?

0

a) dwie wersje biblioteki: jedna z zależnością od Qt, druga bez - z jednego źródła z ifdefami,

b) dwie biblioteki: jedna niezależna od Qt, druga będąca nakładką (fasadą) używającą QString

c) trzy biblioteki: niezależna używająca char* do wykorzystania wszędzie, i osobne fasady używające STL, Qt albo czegoś jeszcze.

Czy jest to możliwe?
Oczywiście, dlaczegóżby nie?

Zalety, wady takiego rozwiązania w przypadku pisania biblioteki?
Upierdliwość w utrzymaniu. Raczej na pewno jeden wariant będzie cię bardziej interesował niż drugi, w jednym coś zmienisz, w drugim zapomnisz...

dlatego uważam że publiczne API biblioteki powinno być jak najprostsze. jeśli to nie jest ogromna biblioteka to niech API będzie kompatybilne z C, czyli same funkcje (bez klas), bez STL-a ani innych bibliotek typowych dla C++.

0

Wewnętrznie biblioteka będzie pisana w Qt na 100% i tutaj nie ma sensu pisania 2 wersji chodzi jedynie o zewnętrzne, publiczne API a jest narzucone "z góry" że ma być "natywne" w projektach Qt czyli QString, QVector, QMap w argumentach i wartościach zwracanych a odpowiedniki std w reszcie projektów i dlatego myślałem o kompilacji warunkowej ale chciałem zapytać bo może są z tym jakieś problemy które mogłyby wyjść dopiero w trakcie a ktoś z was już o nich i wie i może zaoszczędzić mi czasu i nerwów.

0

I jak to wygląda z obsługą błędów? Tutaj też mogę użyć kompilacji warunkowej do kompilacji biblioteki używającej wyjątków lub wersji bez nich?

0
Azarien napisał(a):

dlatego uważam że publiczne API biblioteki powinno być jak najprostsze. jeśli to nie jest ogromna biblioteka to niech API będzie kompatybilne z C, czyli same funkcje (bez klas), bez STL-a ani innych bibliotek typowych dla C++.
Właśnie dlatego ja bym to zrobił w ten sposób, że core biblioteki napisałbym nawet w QT, ale funkcje przyjmowały by char*. Zatem można by było tego użyć w różnych językach jak C/C++/Delphi/C#. Do tego napisałbym wrapper opakowujący to w klasę do QT czy C++. Bo to już niestety będzie zależne od kompilatora. Zaleta tego jest taka, że funkcjonalność zmieniamy w jednym miejscu i już. Wrapper będzie trzeba zmieniać tylko jeśli zmienimy API.

0
Skunk napisał(a):

Wewnętrznie biblioteka będzie pisana w Qt na 100% i tutaj nie ma sensu pisania 2 wersji chodzi jedynie o zewnętrzne, publiczne API a jest narzucone "z góry" że ma być "natywne" w projektach Qt czyli QString, QVector, QMap w argumentach i wartościach zwracanych a odpowiedniki std w reszcie projektów i dlatego myślałem o kompilacji warunkowej ale chciałem zapytać bo może są z tym jakieś problemy które mogłyby wyjść dopiero w trakcie a ktoś z was już o nich i wie i może zaoszczędzić mi czasu i nerwów.
To w takim razie napisz swoją bibliotekę w Qt, a zrób dodatkowy wrapper na typy danych z std i tyle.

0

To w takim razie napisz swoją bibliotekę w Qt, a zrób dodatkowy wrapper na typy danych z std i tyle.

Polecasz napisać wrapper na std więc uzasadnij czemu to rozwiązanie jest lepsze niż dodatkowe "warunkowe" API bezpośrednio w tej samej klasie, w jednym kodzie źródłowym.

0

Każdy taki warunek to dodatkowa szpilka w dupe osoby utrzymującej kod, tyle wystarczy.

1
Skunk napisał(a):

Polecasz napisać wrapper na std więc uzasadnij czemu to rozwiązanie jest lepsze niż dodatkowe "warunkowe" API bezpośrednio w tej samej klasie, w jednym kodzie źródłowym.
Mam kilka powodów:

  • dla mnie osobiście kod z dużą ilością idfefów staje się nieczytelny.
  • wprowadzenie ifdef'ów na poziomie preprocesora będzie powodowało, że będziesz miał kilka wersji bibliotek. Co za tym idzie testowania będzie więcej.
  • ifdef będzie powodował to, że będziesz za każdym razem musiał kompilować projekt ze zmienionymi ustawieniami, trochę więcej ręcznej roboty niż w przypadku API + wrapper.
1

Najlepiej chyba zrobić coś jak basic_string + ew. kompilacja warunkowa.

Czyli robisz klasę szablonową:

basic_moja_klasa<parametry>

moja_klasa - bazująca na basic_moja_klasa<domyslne_parametry>

A użytkownicy mogą sobie sami stworzyć typ taki jak im będzie pasował (z wyjątkami lub bez, bazującą na Qt lub STL stringach).

Przykłady tego podejścia:
std::basic_string & std::string
basic_char_view i char_view: https://github.com/vpiotr/char_view/blob/master/include/char_view.h

Podsumowując: bazujesz bardziej na "policy-based design" zamiast na dyrektywach preprocesora.
https://en.wikipedia.org/wiki/Policy-based_design
http://www.freakycpp.com/
http://www.boost.org/community/generic_programming.html#policy

Edit: dyrektywy preprocesora generują różne wersje biblioteki (czyli trzeba mieć różne .LIB w zależności od użytego ustawienia), policy-based design nie wymaga różnych wersji lib-a, po prostu używasz różnych klas.

0

Dzieki wam bardzo, skoro wszyscy mówicie że rozwiązanie z kompilacją warunkową jest "brzydsze" to coś w tym musi być. Więc zrobie wrapper dla typów std.

A jeszcze powiedzcie jak wygląda sprawa kompilacji warunkowej dla różnego mechanizmu obsługi błędów bo tutaj też mam problem - jedna ekipa koniecznie chce mieć rzucane wyjątki, inna z kolei chce używać w Qt API asynchronicznego ( sygnał / slot ), jeszcze inni chcą kody błedów - jak to wszystko pogodzić? Robiąc tyle wersji zajade się nawet w przypadku małej biblioteki.

Kody błędów i sygnał/slot można w API połączyć bo jeżeli któregoś nie chcemy używać to po prostu nie podłączamy sygnału i tyle natomiast z wyjątkami jest większy problem bo jak zaczne ich używać w kodzie to developer jest na nie już skazany czyż nie?

0

Widzę że w bibliotece boost używają po prostu przeciążonej funkcji dla kodu błędu:

uintmax_t file_size(const path& p);                                          // rzuca wyjątek
uintmax_t file_size(const path& p, system::error_code& ec);

tylko jak do tego dodać jeszcze API asynchroniczne czyli sygnał / slot tak żeby to nie gryzło się z pierwsza metodą która rzuca wyjątek?

A może chce połączyć zbyt wiele rzeczy w jednej bibliotece? Może powinienem wybrać jedną strategie obsługi błędów w bibliotece - z lub bez wyjatków i tyle?

@vpiotr
To co podałeś jest ciekawe i być może spróbuje tego w prywatnym projekcie ale w bibliotece którą mam pisać raczej aż tak nie chciałbym kombinować.

0

Możliwości moim zdaniem masz dwie. Pierwsza to parametr przekazany do konstruktora klasy decydujący o tym czy rzucać wyjątki. Ale to moim zdaniem jest średnie rozwiązanie. Drugie to takie jak zaproponowałeś z przeciążonymi funkcjami. Chociaż ja bym wybrał jedną metodę zwracania błędów. Z wyjątkami.

1
Skunk napisał(a):

Wewnętrznie biblioteka będzie pisana w Qt na 100% i tutaj nie ma sensu pisania 2 wersji chodzi jedynie o zewnętrzne, publiczne API a jest narzucone "z góry" że ma być "natywne" w projektach Qt czyli QString, QVector, QMap w argumentach i wartościach zwracanych a odpowiedniki std w reszcie projektów i dlatego myślałem o kompilacji warunkowej ale chciałem zapytać bo może są z tym jakieś problemy które mogłyby wyjść dopiero w trakcie a ktoś z was już o nich i wie i może zaoszczędzić mi czasu i nerwów.

W tej sytuacji to wpadasz w bagno. Niestety Qt używa singletonów i wiele klas się do nich odnosi. najczęściej są one powiązane z QApplication.
To stwarza pewne ograniczenia co może być użyte w takiej bibliotece Qt a co nie.
Jeśli nie będziesz się pilnował to biblioteka po prostu nie będzie działać, gdy będzie łączona z aplikacją nie używającą Qt (na starcie będziesz miał crash).
To oznacza, że opakowywanie je czystym STL-em po prostu możne nie mieć sensu.

Niestety twój temat jest zbyt ogólny by odpowiedzieć czy jesteś w stanie to zrobić, czy nie.

Sytuacja jest podobna jak z innymi frameworkami, przykładowo można opakować bibliotekę Java tak by była dostępna z C++ (JNI), ale jeśli aplikacja docelowa nie ma wirtualnej maszyny Java to sprawa się bardzo komplikuje.

Nie oznacza to, że nie ma to zupełnie sensu. Przykładowo, robię teraz projekcie, gdzie biblioteki Java, .net, ObjC, Android są opakowywane za wspólnymi interface'ami. Następnie całe to opakowanie jest użyte do napisanie logiki w czystym C++, a następnie ta logika jest opakowywana API C#, ObjC, Java i dostarczana jest wspólna końcowa biblioteka dla wielu platform z API zdefiniowanym w języku charakterystycznym dla danej platformy.
Działa to zaskakująco dobrze.
Jednak problem, że czegoś może brakować do poprawnego działania w tym wypadku nie wystąpi, bo kod C++ jest środkiem kanapki pomiędzy dwoma kawałkami chleba charakterystycznymi dla danej platformy, więc zawsze ma się gwarancję, że wszystko co będzie potrzebne do poprawnego działania biblioteki będzie dostępne.

0

@MarekR22 trochę niepokojące jest to co piszesz. Ja programuję co prawda w C++ Builderze, ale tam jest tak samo. Jest globalny (co mnie drażni) obiekt TApplication. Ale jeśli mam całość zapakowaną w plik *.dll to ta dllka ma własny obiekt TApplication i nawet jak mam exe napisany w Builderze to on ma oddzielny obiekt TApplication. Zatem tu jest tak, że trzeba odpowiednio ustawić pewne rzeczy aby to był ten sam obiekt. Ale nic nie stoi na przeszkodzie zlinkować statycznie biblioteki runtime z VLC'a i wtedy takiej dll'ki można użyć w dowolnym kompilatorze o ile będziemy eksportować czyste funkcje, a nie całe obiekty, bo jak wiadomo to nie jest przenośne między kompilatorami.

0

@MarekR22

Niestety Qt używa singletonów i wiele klas się do nich odnosi. najczęściej są one powiązane z QApplication.
To stwarza pewne ograniczenia co może być użyte w takiej bibliotece Qt a co nie.

A możesz wymienić kilka przykładów klas z którymi może być problem w projektach nie używających Qt?

Generalnie w projekcie używam modułu Xml, kontenerów z Qt - QString, QList, QVector itp. i mechanizmu sygnałów i slotów ale ten z tego co czytałem może być używany w projektach które nie używają Qt( nie wypadaja pętli zdarzeń Qt ).

Z drugiej strony nie chciałbym się ograniczać tym że nie mogę użyć jakiejś klasy ze wzgledu na to że nie zadziała to w wersji dla std.

Nie oznacza to, że nie ma to zupełnie sensu. Przykładowo, robię teraz projekcie, gdzie biblioteki Java, .net, ObjC, Android są opakowywane za wspólnymi interface'ami. Następnie całe to opakowanie jest użyte do napisanie logiki w czystym C++, a następnie ta logika jest opakowywana API C#, ObjC, Java i dostarczana jest wspólna końcowa biblioteka dla wielu platform z API zdefiniowanym w języku charakterystycznym dla danej platformy.

Czy jesteś w stanie podać przykład jakiejkolwiek choćby najmniejszej funkcji jak to wygląda od "wspólnego interfejsu" po "natywne" API?

To może łatwiej będzie pierwszą wersje napisać z wykorzystaniem Qt tylko tam gdzie to konieczne ( jak np moduł Xml ) a druga wersje zrobic dla Qt i w tej wersji opakowywać interfejs z std?

0

Generalnie w projekcie używam modułu Xml, kontenerów z Qt - QString, QList, QVector itp. i mechanizmu sygnałów i slotów

Zamiast kłopotliwego Qt proponuję użyć jednak STL, ewentualnie Boosta, z którymi nie ma problemów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1