Parere devi C++

Cum vi se pare lucrul cu boost::asio pentru un junior dev?
Momentan am primit un proiect ce trebuie finalizat si nu e deloc documentat. Are o structura relativ ciudata. E cumva un nivel in plus de abstractizare asupra boost::asio. Din ce am vazut (un lucru contra intuitiv), prezinta clase abstracte si foloseste la un moment dat smart pointers care nu indica spre nimic (un fel de stadiu abandonat), template-ul lor folosind un tip de date din interiorul unei clase abstracte (care nu are metodele sale implementate nicaieri), avand cam accelasi nume + terminata tPtr (weird design), care e si ea inexistenta. De asemenea, pentru cei care au mai lucrat cu boost, programul nu prezinta niciun obiect io_service, iar partea de setup pt socket e cumva divizata in alte clase. Cam orice chestie prezinta un fisier NumeChestieAbs. Mi se pare un design foarte ciudat. Evident, nu pot partaja codul, dar voiam punctul vostru de vedere. Ma simt relativ depasit de situatie si eram curios daca sunt eu underskilled sau desing-ul asta e foarte ciudat.

Comunicarea server-client se realizeaza asincron.

Smart pointers către clase abstracte se folosesc pentru a putea modifica comportamentul funcțiilor la runtime. Da, clasele abstracte nu au metodele implementate direct, de-aia e abstractă (deși, fun fact: în C++ se pot implementa metodele claselor abstracte direct în clasa aceea), dar acele metode sunt implementate în clase care moștenesc clasa aia abstractă. Așa pot să am aceeași interfață, dar cu mai multe implementări, pe care le pot alege la runtime în funcție de cum vreau. Având în vedere că alegerea se face la runtime și vrei comportament polymorphic, nu ai altă opțiune decât să folosești pointers către memorie alocată pe heap, pe stack nu prea ai avea cum să știi dinainte ce mărime să aibă obiectul. Ca să vezi implementarea completă, caută clasele care moștenesc clasa asta abstractă.

io_service e înlocuit de io_context, nu are cum să nu folosească io_context.

Personal nu sunt fan boost, mi se pare că are niște interfețe extrem de complicate fără motive foarte bune. C și C++ mi se par oribile pentru networking în general oricum, dar boost mai are și o interfață cumbersome. Nu ai decât să citești documentația boost care are niște tutoriale pentru început, ca să înțelegi modelul de async computations. Mai nou asio e integrat și cu corutinele din C++20 (și era și înainte integrat cu boost::coroutine), aia ar trebui să fie mai ușor de înțeles. Dar la bază e același principiu.

Edit: poate îți e mai ușor dacă te uiți peste epoll sau kqueue ca să înțelegi cum funcționează asio. La bază asio e un wrapper peste syscalls de genul.

1 Like

Multumesc pentru sugestie, apreciez raspunsul. Nu faptul ca exista un pointer catre o clasa abstracta ma incurca pe mine. Stiu cum functioneaza polimorfismul. Din punct de vedere al OOP-ului nu intampin probleme. Problema care ma zapacea pe mine e structura codului. De exemplu am shared_ptr ClasaAbstracta1::ClasaAbstracta1_Mptr ptr, unde ClasaAbstracta1_Mptr, nu exista. Codul are foarte multe goluri de genul. Acel shared pointer nu foloseste un make_shared, dar e folosit prin program…
Proiectul pe care lucrez e efectiv un cod nefinalizat, si care foloseste asio intr-un mod haotic.

Dacă nu există clasa, trebuie să dea eroare de compilare.

  • Folosește neapărat un language server precum clangd ca să poți da go to definition.
  • Vezi dacă nu cumva clasele astea care nu există sunt în interiorul vreunui #if/#ifdef.
  • Poți să încerci să rulezi Doxygen peste codebase ca să îți dea niște diagrame cu relațiile între clase.

Este prea complex pentru un junior. Cel mai bine intrebi lead-ul sau managerul daca task-ul asta este potrivit pentru nivelul tau.

Eu am intrat in el cand aveam vreo 3 ani de experienta.

1 Like

Smart pointers se folosesc frecvent cu boost asio pentru a tine clasa alive in callbacks.

Poate ai cumva si PImpl - cppreference.com pe acolo?

Documentatie buna boost Chapter 32. Boost.Asio

De acord ca boost asio in productie nu-i pentru juniori.

Poate clasa ta ClasaAbstracta1_Mptr e declarata undeva (header?) cu class ClasaAbstracta1_Mptr; si asa poti sa faci un shared_ptr cu ea (doar la unique_ptr nu ii convine un incomplete type si nu ar merge la link in cazul asta simplu). Poate ai un pimpl pe undeva pe acolo, cum s-a mai zis sau e doar un stub sau ceva necesar pentru lib asta.

Folosim boost la munca si nu imi place deloc (compile time oribil, prea complex etc.) dar nu am avut treaba cu asio, deci nu stiu ce sa zic. E ok daca nu ai alternative, I guess? Poate e mai ok ca restul din boost.

Acum vreo 40 de ani dezvoltatorii sistemului de operare BSD au introdus un API pentru comunicare in retea (si nu numai): Berkeley sockets - Wikipedia

A fost adoptat de mai toate sistemele de operare. Chiar si Windows: Windows Sockets: Background | Microsoft Learn

Daca exista deja un API, si mai e si la fel peste tot, de ce ne mai trebuie altul?

Fiindca doar facilitatiile de baza sunt aceleasi. Poti scrie aproape acelasi cod pe Windows si Linux pentru a accepta un client, dar ce faci cand vrei sa comunici cu 10.000 in acelasi timp?

Unii devs ar crea cate un thread pentru fiecare client, doar ca asta e printre cele mai ineficiente metode. Solutia eficienta depinde de sistemul de operare, fiindca fiecare a gasit alte abordari.

Asa ca APIul nu mai e chiar la fel.

Cum facem sa scriem acelasi cod pentru mai multe sisteme de operare?

Au aparut biblioteci ce abstractizeaza detaliile. Cele mai cunoscute in lumea C si C++:

Acestea contin cod specific fiecarui sistem de operare modern si abstractizeaza totul sub un API propriu.

Ce-i asa greu la boost::asio?

Daca stapanesti C++ si ai mai scris cod similar, folosind de exemplu direct APIul Linux, n-ar trebui sa-ti ia mai mult de cateva ore sa te obisnuiesti cu boost asio.

Daca insa nu mai avut treaba cu asa ceva, te poti lovi usor de cod care pare sa mearga bine acum, dar in unele cazuri nu face ce trebuie.

Unde e problema initiatorului defapt?

Boost asio iti pune pe tava APIul de comunicare. Dar ce comunici defapt? Daca ai ajuns sa scrii cod la nivel de sockets mai mult ca sigur ca te-ai apucat sa implementezi si un protocol de comunicare, sau, mai rau, sa inventezi unul nou.

Treaba asta e mai complexa decat simpla comunicare, si nu depinde de APIul folosit pentru a comunica in retea. Cred ca aici s-a incercat o implementare nu foarte simpla in cazul lui @Adrian_stefan

Ce protocol folositi? N-ati gasit o biblioteca care sa-l implementeze deja eficient?

2 Likes

O intrebare foarte buna in legatura cu protocolul. In source code exista un enum (care contine doar 1 singur tip de conexiune) . De ce enum? Nu pot stii.
image

TCP e un protocol, dar e implementat deja de sistemul de operare.

Ce protocol folositi peste TCP?

Poate e o chestie de C pentru a tine constantele.

1 Like

Sincer este cam aiurea sa dea cineva un raspuns decent la topicul asta. Pare mai mult o descarcare de frustrare combinata cu o nevoie de confirmare mai mult decat o intrebare specifica. Si de ce spun asta (ca sa nu ma intelegi gresit).

  • Nu prea exista separare intre libraria desemnata tie si boost::asio. Adica, uneori nu stiu daca vorbesti de libraria ta sau de boost::asio. Si despre ce anume. Nu iti convine ceva la libraria ta sau la designul de la boost::asio.
  • Nu prea dezvalui ce trebuie sa faca libraria/aplicatia respectiva. Inteleg motivul (uneori). Dar cand vorbesti despre ceva la mod abstract atunci si raspunsul este tot abstract. Si o dai dintr-una intr-alta pana pierzi sirul sau cu ce vroiai sa te lamuresti initial.

Adica da, pot sa vin si sa regurgitez aceeasi chestie pe care probabil o gasesti in 98% din discutiile asemanatoare despre boost. Si anume:

  • Ridicol de complex pentru perioada asta. Inteleg necesitatea acum 15+ ani. Dar azi, nu prea.
  • Curba de invatare oribila. Nici nu trebuie sa explic de ce sau cum.
  • Compile time oribil. Datorita abuzului de meta-programming se vede rezultatul.
  • Erori criptice si cat enciclopedia. Din nou, abuz meta-programming si se vede.
  • Setup oribil. Vine cu porcariile lui. Mai greu de integrat aici.

Si non-contra:

  • Il folosesti de nevoie. Nu de placere. Daca cineva te contrazice aici. Mergi mai departe. Sau mai degraba, fugi.
  • Un singur code-base unificat. Nu stai sa importi 10 librarii si fiecare cu tampeniile lor. Te trezesti cu 7 implementatii de str::string/std::vector. Daca sunt librarii care isi fac reclama ca nu depind de STL (se gasesc)
  • Si alte chestii care isi pierd pozitivitatea pe zi ce trece. Deci nu are rost sa le mentionezi.

Adica pot sa vin si cu un reply generic. Nu ma deranjeaza (daca te ajuta cumva sa iti confirm parerea deja formata). Dar sa ceri o explicatie la explicatia ta. Putin contraintuitiv, nu crezi?

Era mai eficient daca bagai si un exemplu mic cu ce te deranjeaza pe tine acolo. Era mai usor de inteles si explicat de ce a luat cineva decizia aia cand a scris codul ala.

Adica sa nu ma intelegi gresit. Incerc doar sa inteleg ce vrei. Nu imi arde de circ sau sa “bat campii” prin forumuri.

1 Like

As zice totusi ca cel mai probabil e scris in ideea ca poate in viitor va fi extinsa logica cu alte tipuri. Sunt destui care scriu codul in felul asta (eu in general ma gandesc la YAGNI cand vad asta), in special cei cu background in Java/C#.

plus ca un enum este cica mai type safe.

Acel typedef si C drept prefix imi indica cineva care scrie C++ in stilul de acum 20 de ani.

Acel enum doar cu TCP in el imi indica ca cineva n-a inteles prea bine cum e cu retelistica.

Un enum simplu nu prea e „mai“ type-safe. Face decay la int fără probleme. enum class ar fi mai type-safe, să zicem, că te obligă să faci manual static_cast dacă vrei să obții int-ul.

Also, typedef enum e semn că e C++ scris ca și cum ar fi C. C++ nu mai are nevoie de acel typedef (în C++ există using oricum).

Da, dar asta este un soi de conversie “safe”. Invers, dacă funcția așteapta un argument de tipul acelui enum nu mai merge conversia, și de fapt asta e ceea ce ne interesează.

De exemplu, în switch-ul de mai jos nu mai este nevoie să tratezi cazul “default”, pentru ca nu este posibil sa apară (mă rog, mai puțin dacă ți-o cauți cu lumânarea și folosești cast, dar pe scenariul ăsta e posibil să faci cast și la clase de tip incompatibil și să faci totul varză).

enum Color { red, green, blue };
Color r = red;
 
switch(r)
{
    case red  : std::cout << "red\n";   break;
    case green: std::cout << "green\n"; break;
    case blue : std::cout << "blue\n";  break;
}

Acum depinde și ce înseamnă type-safe pentru un enum. Faptul că face decay la int implicit pentru mine nu prea e type-safe. De-asta folosesc enum class by default.

Lasand la o parte amanuntele unui enum, care ar fi al doilea membru care sa justifice existenta acelui enum?

Poate în viitor se avea în vedere o implementare UDP-based?

1 Like