Tło / scenariusz
Zacząłem pisać aplikację CLI wyłącznie w C (mój pierwszy właściwy program w C lub C ++, który nie był „Hello World” ani jego odmianą). Mniej więcej w połowie pracowałem z „ciągami znaków” wprowadzanymi przez użytkownika (tablice znaków) i odkryłem obiekt streamera ciągów znaków C ++. Widziałem, że mogę zapisać kod, używając ich, więc użyłem ich za pośrednictwem aplikacji. Oznacza to, że zmieniłem rozszerzenie pliku na .cpp i teraz kompiluję aplikację g++
zamiast gcc
. Opierając się na tym, powiedziałbym, że aplikacja jest teraz technicznie aplikacją C ++ (chociaż 90% + kodu jest napisane w tym, co nazwałbym C, ponieważ istnieje wiele skrzyżowań między tymi dwoma językami, biorąc pod uwagę moje ograniczone doświadczenie w dwójka). Jest to pojedynczy plik .cpp o długości około 900 linii.
Ważne czynniki
Chcę, aby program był darmowy (jak w pieniądzu), mógł być swobodnie dystrybuowany i użyteczny dla wszystkich. Obawiam się, że ktoś spojrzy na kod i pomyśli coś z efektem:
Och, spójrz na kodowanie, to okropne, ten program nie może mi pomóc
Gdy potencjalnie może! Inną kwestią jest efektywność kodu (jest to program do testowania łączności Ethernet). Nie powinno być żadnych części kodu, które byłyby tak nieefektywne, że mogłyby poważnie utrudnić działanie aplikacji lub jej wyników. Myślę jednak, że to pytanie dotyczy przepełnienia stosu, gdy prosi o pomoc w zakresie określonych funkcji, metod, wywołań obiektów itp.
Moje pytanie
Posiadanie (moim zdaniem) mieszania C i C ++ tam, gdzie być może nie powinienem. Powinienem spróbować przepisać to wszystko w C ++ (mam na myśli implementację większej liczby obiektów i metod C ++, w których być może kodowałem coś w stylu C, które można skondensować przy użyciu nowszych technik C ++), lub usunąć użycie obiektów do streamingu łańcuchów i przywrócić to wszystko z powrotem do kodu C? Czy jest tu właściwe podejście? Jestem zagubiony i potrzebuję wskazówek, jak zachować tę aplikację „dobrą” w oczach mas, aby mogli z niej korzystać i czerpać z niej korzyści.
Kod - aktualizacja
Oto link do kodu. To około 40% komentarzy, komentuję prawie każdą linię, dopóki nie poczuję się bardziej płynnie. W kopii, do której odsyłam, usunąłem prawie wszystkie komentarze. Mam nadzieję, że nie utrudni to czytania. Mam jednak nadzieję, że nikt nie powinien tego w pełni rozumieć. Jeśli jednak popełniłem fatalne wady projektowe, mam nadzieję, że powinny być łatwe do zidentyfikowania. Powinienem także wspomnieć, że piszę kilka komputerów stacjonarnych i laptopów Ubuntu. Nie zamierzam przenosić kodu do innych systemów operacyjnych.
LICENSE
pliku. Możesz uzyskać ciekawe informacje zwrotne.Odpowiedzi:
Zacznijmy od początku: mieszany kod C i C ++ jest dość powszechny. Więc na początek jesteś w dużym klubie. Na wolności mamy ogromne bazy kodów C. Ale z oczywistych powodów wielu programistów odmawia pisania co najmniej nowych rzeczy w C, mając dostęp do C ++ w tym samym kompilatorze, nowe moduły zaczynają być pisane w ten sposób - na początku po prostu pozostawiając istniejące części w spokoju.
Następnie niektóre istniejące pliki zostaną ponownie skompilowane jako C ++, a niektóre mosty można usunąć ... Ale może to potrwać naprawdę długo.
Trochę wyprzedzasz, twój pełny system to teraz C ++, tylko większość z nich jest napisana w stylu „C”. I widzisz, że mieszanie stylów stanowi problem, czego nie powinieneś: C ++ jest językiem o wielu paradygmatach, obsługującym wiele stylów i pozwalającym im na dobre współistnienie. W rzeczywistości jest to główna siła, że nie jesteś zmuszony do jednego stylu. Taki, który byłby nieoptymalny tu i tam, przy odrobinie szczęścia nie wszędzie.
Ponowna praca bazy kodu jest dobrym pomysłem, JEŚLI jest zepsuta. Lub jeśli jest to na drodze rozwoju. Ale jeśli to działa (w oryginalnym znaczeniu tego słowa), należy postępować zgodnie z najbardziej podstawową zasadą inżynierii: jeśli się nie zepsuło, nie naprawiaj go. Pozostaw zimne części w spokoju, włóż swój wysiłek tam, gdzie się liczy. Na częściach, które są złe, niebezpieczne - lub w nowych funkcjach, po prostu przerób części, aby stały się łóżkiem.
Jeśli szukasz ogólnych rzeczy do rozwiązania, oto, co warto eksmitować z bazy kodu C:
Kiedy skończysz z tym, zbliżasz się do punktu, w którym podstawa kodu może być w zasadzie bezpieczna dla wyjątków, więc możesz upuścić futbolowy kod powrotu, używając wyjątków i rzadkiego try / catch tylko w funkcjach wysokiego poziomu.
Poza tym po prostu napisz nowy kod w jakimś zdrowym C ++, a jeśli rodzą się pewne klasy, które są dobrym zamiennikiem w istniejącym kodzie, podnieś je.
Nie wspominałem o rzeczach związanych ze składnią, oczywiście używam referencji zamiast wskaźników w nowym kodzie, ale zastępowanie starych części C tylko dla tej zmiany nie jest dobrą wartością. Odlewy, które musisz rozwiązać, wyeliminować wszystko, co możesz, i użyć wariantów C ++ w funkcjach otoki dla pozostałej części. I bardzo ważne, dodaj const wszędzie tam, gdzie ma to zastosowanie. Przeplatają się one z wcześniejszymi pociskami. I konsoliduj swoje makra i zamień to, co możesz zrobić na wyliczanie, funkcję wbudowaną lub szablon.
Sugeruję przeczytanie standardów kodowania C ++ Sutter / Alexandrescu, jeśli jeszcze tego nie zrobiono, i ściśle ich przestrzegać.
źródło
Krótko mówiąc: nie pójdziesz do piekła, nic złego się nie stanie, ale nie wygrasz też żadnych konkursów piękności.
Chociaż użycie C ++ jako „lepszego C” jest całkowicie możliwe, odrzucasz wiele zalet C ++, ale ponieważ nie ograniczasz się do wanilii C, nie zyskujesz żadnych korzyści z C (prostota, przenośność , przejrzystość, interoperacyjność). Innymi słowy: C ++ poświęca niektóre cechy C, aby zyskać inne - na przykład przezroczystość C, w której zawsze można wyraźnie zobaczyć, gdzie i kiedy następuje alokacja pamięci, jest wymieniana na silniejsze abstrakcje C ++.
Ponieważ wydaje się, że Twój kod działa teraz poprawnie, prawdopodobnie nie jest dobrym pomysłem przepisanie go tylko ze względu na to: zachowaj go takim, jakim jest na teraz, i zmień go na bardziej idiomatyczny C ++ w miarę upływu czasu, po jednym kawałku, za każdym razem, gdy pracujesz nad jakąkolwiek częścią. I pamiętaj o wyciągniętych wnioskach o następnym projekcie, przede wszystkim o tym, że C i C ++ nie są tym samym językiem, i lepiej jest dokonać wyboru z góry, niż decydować się na przejście do C ++ w połowie projektu .
źródło
C ++ jest bardzo złożonym językiem, w tym sensie, że ma wiele funkcji. Jest to również język, który obsługuje wiele paradygmatów, co oznacza, że pozwala pisać całkowicie kod proceduralny bez użycia żadnych obiektów.
Ponieważ C ++ jest tak złożony, często widzisz, że ludzie używają ograniczonego podzbioru jego funkcji w swoim kodzie. Początkujący często używają po prostu strumieniowego wejścia / wyjścia, obiektów łańcuchowych i nowego / delete zamiast malloc / free, a może referencji zamiast wskaźników. Gdy poznasz funkcje obiektowe, możesz zacząć pisać w stylu „C z klasami”. W końcu, gdy dowiesz się więcej o C ++, zaczniesz używać szablonów, RAII , STL , inteligentnych wskaźników itp.
Chodzi mi o to, że nauka C ++ jest procesem wymagającym czasu. Tak, teraz twój kod prawdopodobnie wygląda tak, jakby został napisany przez programistę C próbującego napisać C ++. A ponieważ dopiero się uczysz, to jest całkowicie w porządku. Czuję jednak dreszcze, gdy widzę coś takiego w kodzie produkcyjnym napisanym przez doświadczonych programistów, którzy powinni wiedzieć lepiej.
Pamiętaj tylko, że dobry programista Fortran może pisać dobry kod Fortran w dowolnym języku. :)
źródło
Wygląda na to, że podsumowujesz dokładnie ścieżkę wielu starych programistów C, przechodząc do C ++. Używasz go jako „lepszego C”. Dwadzieścia lat temu miało to jakiś sens, ale w tym momencie w C ++ jest tak wiele potężniejszych konstrukcji (jak Standardowa Biblioteka Szablonów), że obecnie rzadko ma to sens. Przynajmniej powinieneś wykonać następujące czynności, aby uniknąć dawania tętniaków programistom C ++, gdy patrzysz na swój kod:
printf
rodzina.new
zamiastmalloc
.std::string
zamiastchar*
tam, gdzie to możliweJeśli twój program jest krótki (a ~ 900 wierszy wydaje się krótki) osobiście nie sądzę, aby próba stworzenia solidnego zestawu klas była wymagana, a nawet przydatna.
źródło
Przyjęta odpowiedź wymienia tylko zalety konwersji C na idiomatyczny C ++, tak jakby kod C ++ był w pewnym sensie absolutnie lepszy niż kod C. Zgadzam się z innymi odpowiedziami, że prawdopodobnie nie jest konieczne wprowadzanie drastycznych zmian w mieszanym kodzie, jeśli nie jest uszkodzony.
To, czy mieszanie C i C ++ jest zrównoważone, zależy od sposobu mieszania. Subtelne błędy można wprowadzić na przykład, gdy w kodzie C zgłaszane są wyjątki (które nie są bezpieczne), powodując wycieki pamięci lub uszkodzenia danych. Bezpieczniejszym i dość powszechnym sposobem jest owijanie bibliotek C lub interfejsów w klasy lub używanie ich do innego rodzaju izolacji w projekcie C ++.
Zanim zdecydujesz się przepisać cały projekt na idiomatic C ++ (lub C), powinieneś mieć świadomość, że wiele zmian przedstawionych w innych odpowiedziach może spowolnić program lub wywołać inne niepożądane efekty. Na przykład:
Podsumowując, konwersję mieszanego kodu C i C ++ do idiomatycznego C ++ należy postrzegać jako kompromis, który ma o wiele więcej niż tylko czas implementacji i powiększenie zestawu narzędzi o pozornie wygodne funkcje. Dlatego bardzo trudno jest udzielić odpowiedzi w ogólnym przypadku innym niż „to zależy”.
źródło
Mieszanie C i C ++ to zła forma. Są to odrębne języki i naprawdę powinny być traktowane jako takie. Wybierz ten, który najbardziej ci odpowiada i spróbuj napisać kod idiomatyczny w tym języku.
Jeśli C ++ oszczędza ci dużo kodu, trzymaj się C ++ i ponownie napisz części C. Wątpię, czy różnica w wydajności będzie zauważalna, jeśli o to się martwisz.
źródło
Cały czas poruszam się między C i C ++ i jestem dziwakiem, który woli pracować w ten sposób. Wolę C ++ do rzeczy na wyższym poziomie, podczas gdy używam C jako tępego narzędzia, które pozwala mi prześwietlać typy danych i traktować je jak bity i bajty za pomocą, powiedzmy,
memcpy
przydatnej do implementacji struktur danych niskiego poziomu i alokatorów pamięci . Jeśli czujesz, że pracujesz na poziomie surowych bitów i bajtów, to naprawdę bogaty system typów C ++ w ogóle nic nie pomaga i często łatwiej jest mi napisać taki kod w C, gdzie mogę bezpiecznie założyć, że mogę traktuj dowolny typ danych C jak tylko bity i bajty.Najważniejszą rzeczą do zapamiętania jest to, że te dwa języki nie łączą się dobrze.
1. Funkcja AC nigdy nie powinna wywoływać funkcji C ++, która może
throw
. Biorąc pod uwagę liczbę miejsc, w których twój codzienny kod C ++ może pośrednio wystąpić jako wyjątek, oznacza to ogólnie, że twoje funkcje C nie powinny ogólnie wywoływać funkcji C ++. W przeciwnym razie kod C nie będzie w stanie zwolnić zasobów, które alokuje podczas rozwijania stosu, ponieważ nie ma praktycznego sposobu na złapanie wyjątku C ++. Jeśli w końcu będziesz potrzebować kodu C do wywołania funkcji C ++ jako wywołania zwrotnego, np. Strona C ++ powinna upewnić się, że każdy napotkany wyjątek zostanie wychwycony przed powrotem do kodu C.2. Jeśli napiszesz kod C, który traktuje typy danych jako zwykłe bity i bajty, spychając system typów (to właściwie mój główny powód używania C w pierwszej kolejności), to nigdy nie chcesz używać takiego kodu w stosunku do danych C ++ typy, które mogą mieć konstruktory kopiujące i destruktory oraz wirtualne wskaźniki i tego rodzaju rzeczy, których należy przestrzegać. Ogólnie więc powinien to być kod C używający tego typu kodu C, a nie kod C ++ używający tego typu kodu C.
Jeśli chcesz, aby Twój kod C ++ używał takiego kodu C, to zazwyczaj chcesz go użyć jako szczegółów implementacyjnych klasy, która upewnia się, że dane, które są przechowywane w ogólnej strukturze danych C, są typem danych, które są łatwe do zbudowania i zniszczyć. Na szczęście istnieją cechy typu jak ten , który można użyć do sprawdzenia, czy w assert statycznej, upewniając się, że typy są przechowywane w strukturze rodzajowej C dane są trywialne destruktorów i konstruktorów i pozostanie w ten sposób.
Moim zdaniem nie musisz się martwić, jeśli będziesz przestrzegać powyższych zasad i upewnisz się, że kod jest dobrze przetestowany pod kątem testów, które napisałeś. Część, którą warto ulepszyć, to kod napisany do tej pory w C ++, ale to nie znaczy, że musisz przenieść kod ściśle oparty na C na C ++.
Z kodem, który napisałeś w C ++ i kompilujesz jako kod C ++, musisz być ostrożny. Używanie kodowania podobnego do C w C ++ wymaga problemów. Na przykład, jeśli przydzielasz i zwalniasz zasoby ręcznie, istnieje szansa, że Twój kod nie jest bezpieczny pod kątem wyjątków, ponieważ kod może napotkać wyjątek, w którym niejawnie wyjdziesz z funkcji, zanim zdołasz kiedykolwiek
free
zasób. W C ++, RAII i rzeczy takie jak inteligentne wskaźniki nie są puszystą wygodą, ponieważ mogą się pojawić, jeśli spojrzysz tylko na normalne ścieżki wykonywania bez uwzględnienia wyjątkowych ścieżek. Często są podstawową koniecznością pisania poprawnego kodu bezpiecznego dla wyjątków w prosty i skuteczny sposób, który nie zacznie przeciekać wszędzie po napotkaniu wyjątku.źródło