Pracuję więc nad projektem oprogramowania wykorzystującym C dla określonego procesora. Zestaw narzędzi obejmuje możliwość kompilacji zarówno C, jak i C ++. Do tego, co robię, w tym środowisku nie jest dostępna dynamiczna alokacja pamięci, a program jest ogólnie dość prosty. Nie wspominając o tym, że urządzenie prawie nie ma mocy procesora ani zasobów. Naprawdę nie ma potrzeby używania jakiegokolwiek C ++.
To powiedziawszy, jest kilka miejsc, w których wykonuję przeładowanie funkcji (cecha C ++). Muszę wysłać kilka różnych typów danych i nie mam ochoty używać printf
formatowania stylu z jakimś %s
argumentem (lub czymkolwiek). Widziałem osoby, które nie miały dostępu do kompilatora C ++ printf
, ale w moim przypadku obsługa C ++ jest dostępna.
Teraz jestem pewien, że mogę uzyskać pytanie, dlaczego muszę przeciążać funkcję na początek. Spróbuję teraz odpowiedzieć na to pytanie. Muszę przesyłać różne typy danych przez port szeregowy, więc mam kilka przeciążeń, które przesyłają następujące typy danych:
unsigned char*
const char*
unsigned char
const char
Wolałbym nie mieć jednej metody, która obsługiwałaby wszystkie te rzeczy. Kiedy wzywam funkcji po prostu chcę to przekazać z portu szeregowego, nie mam dużo zasobów, więc nie chcę robić ledwo COKOLWIEK ale mój transmisji.
Ktoś inny widział mój program i zapytał: „Dlaczego używasz plików CPP?” To mój jedyny powód. Czy to zła praktyka?
Aktualizacja
Chciałbym odpowiedzieć na kilka zadanych pytań:
Obiektywna odpowiedź na twój dylemat będzie zależeć od:
Czy rozmiar pliku wykonywalnego znacznie wzrośnie, jeśli użyjesz C ++.
Obecnie rozmiar pliku wykonywalnego zajmuje 4,0% pamięci programu (z 5248 bajtów) i 8,3% pamięci danych (z 342 bajtów). To znaczy, kompilacja dla C ++ ... Nie wiem, jak by to wyglądało dla C, ponieważ nie korzystałem z kompilatora C. Wiem, że ten program już nie będzie się rozwijał, więc z uwagi na ograniczone zasoby powiedziałbym, że nie mam nic przeciwko ...
Czy istnieje zauważalny negatywny wpływ na wydajność, jeśli używasz C ++.
Cóż, jeśli tak, nic nie zauważyłem ... ale z drugiej strony to może dlatego zadaję to pytanie, ponieważ nie do końca rozumiem.
Określa, czy kod może być ponownie użyty na innej platformie, na której dostępny jest tylko kompilator C.
Wiem, że odpowiedź na to pytanie jest zdecydowanie przecząca . Zastanawiamy się nad przejściem na inny procesor, ale tylko bardziej wydajne procesory oparte na ARM (które, jak wiem, mają łańcuchy narzędzi kompilatora C ++).
//
komentarze. Jeśli to działa, dlaczego nie?//
komentarze są w standardzie C od C99.Odpowiedzi:
Nie posunąłbym się tak daleko, że nazwałbym to „złą praktyką” per se , ale nie jestem też przekonany, że to naprawdę właściwe rozwiązanie twojego problemu. Jeśli wszystko, czego potrzebujesz, to cztery osobne funkcje do obsługi twoich czterech typów danych, dlaczego nie zrobisz tego, co programiści C zrobili od niepamiętnych czasów:
Tak naprawdę kompilator C ++ robi to za kulisami i nie jest to tak duże obciążenie dla programisty. Pozwala uniknąć wszystkich problemów związanych z „dlaczego piszesz niezupełnie C w kompilatorze C ++” i oznacza, że nikt inny w twoim projekcie nie będzie się mylić, które bity C ++ są „dozwolone”, a które nie.
źródło
#define
transmitować () za pomocą C11_Generic
Używanie tylko niektórych funkcji C ++, podczas gdy w przeciwnym razie traktowanie go jako C nie jest dokładnie powszechne, ale również nie jest niespotykane. W rzeczywistości niektóre osoby nawet nie używają żadnych funkcji w C ++, z wyjątkiem bardziej rygorystycznego i wydajniejszego sprawdzania typów. Po prostu piszą w C (starając się pisać tylko na wspólnym przecięciu C ++ i C), a następnie kompilują z kompilatorem C ++ do sprawdzania typu i kompilatorem C do generowania kodu (lub po prostu trzymają się kompilatora C ++ do końca).
Linux jest przykładem bazy kodowej, w której ludzie regularnie proszą, aby identyfikatory takie jak
class
zmieniono nazwy naklass
lubkclass
, aby mogli skompilować Linuksa za pomocą kompilatora C ++. Oczywiście, biorąc pod uwagę opinię Linusa na temat C ++ , zawsze zostają zestrzeleni: - D GCC jest przykładem bazy kodowej, która najpierw została przekształcona w „C ++ - czyste C”, a następnie stopniowo zrestrukturyzowana, aby używać więcej funkcji C ++.Nie ma nic złego w tym, co robisz. Jeśli naprawdę jesteś paranoikiem w kwestii jakości generowania kodu kompilatora C ++, możesz użyć kompilatora takiego jak Comeau C ++, który kompiluje się do C jako języka docelowego, a następnie użyć kompilatora C. W ten sposób możesz również przeprowadzać inspekcje na miejscu kodu, aby sprawdzić, czy użycie C ++ wstrzykuje nieprzewidziany kod wrażliwy na wydajność. Nie powinno tak być w przypadku zwykłego przeciążenia, które dosłownie automatycznie generuje funkcje o różnych nazwach - IOW, dokładnie to, co i tak robiłbyś w C.
źródło
Obiektywna odpowiedź na twój dylemat będzie zależeć od:
Jeśli odpowiedź na którekolwiek z pytań brzmi „tak”, być może lepiej byłoby utworzyć funkcje o różnych nazwach dla różnych typów danych i trzymać się C.
Jeśli odpowiedzi na wszystkie pytania brzmią „nie”, nie widzę powodu, dla którego nie powinieneś używać C ++.
źródło
Zadajesz pytanie, jakby kompilacja w C ++ po prostu dała ci dostęp do większej liczby funkcji. Nie o to chodzi. Kompilacja z kompilatorem C ++ oznacza, że Twój kod źródłowy jest interpretowany jako źródło C ++, a C ++ jest innym językiem niż C. Oba mają wspólny podzbiór wystarczająco duży, aby użytecznie programować, ale każdy ma funkcje, których brakuje w drugim, i jest to możliwe jest napisanie kodu, który jest akceptowalny w obu językach, ale interpretowany inaczej.
Jeśli naprawdę wszystko, czego chcesz, to przeciążenie funkcji, to naprawdę nie widzę sensu mylenia tego problemu poprzez wprowadzenie do niego C ++. Zamiast różnych funkcji o tej samej nazwie, wyróżniłem ich listy parametrów, po prostu napisz funkcje o różnych nazwach.
Jeśli chodzi o twoje obiektywne kryteria,
Plik wykonywalny może być nieco większy po skompilowaniu jako C ++, w porównaniu do podobnego kodu C zbudowanego jako taki. Plik wykonywalny C ++ będzie miał co najmniej wątpliwą zaletę manipulowania nazwami C ++ dla wszystkich nazw funkcji, o ile wszystkie symbole zostaną zachowane w pliku wykonywalnym. (I w rzeczywistości to właśnie zapewnia twoje przeciążenia.) To, czy różnica jest wystarczająco duża, aby była dla Ciebie ważna, należy ustalić na podstawie eksperymentu.
Wątpię, czy zauważysz zauważalną różnicę w wydajności opisanego kodu w porównaniu z hipotetycznym analogiem czystego C.
Rzuciłbym nieco inne światło na to: jeśli chcesz połączyć kod, który budujesz za pomocą C ++ z innym kodem, to albo ten inny kod będzie musiał być napisany w C ++ i zbudowany w C ++, albo będziesz potrzebować wprowadzić specjalne postanowienia w swoim własnym kodzie (deklarując powiązanie „C”), czego dodatkowo nie można zrobić w przypadku przeciążonych funkcji. Z drugiej strony, jeśli kod jest napisany w C i skompilowany jako C, inni mogą połączyć go z modułami C i C ++. Tego rodzaju problem zwykle można rozwiązać, przewracając tabele, ale skoro tak naprawdę nie potrzebujesz C ++, to dlaczego akceptujesz taki problem w pierwszej kolejności?
źródło
Wcześniej użycie kompilatora C ++ jako „lepszego C” było promowane jako przypadek użycia. W rzeczywistości wczesne C ++ było dokładnie tym. Podstawową zasadą projektowania było to, że można było korzystać tylko z żądanych funkcji i nie ponosić kosztów funkcji, których nie używaliście. Możesz więc przeciążać funkcje (deklarując zamiar za pomocą
overload
słowa kluczowego!), A reszta twojego projektu nie tylko skompiluje się dobrze, ale wygeneruje kod nie gorszy niż kompilator C.Od tego czasu języki się nieco rozeszły.
Każdy
malloc
w kodzie C będzie błędem niedopasowania typu - w twoim przypadku nie będzie problemu bez pamięci dynamicznej! Podobnie, wszystkie twojevoid
wskaźniki w C będą cię potykać, ponieważ będziesz musiał dodać jawne rzutowania. Ale… dlaczego to robisz w ten sposób … będziesz prowadzony coraz częściej.Może to być możliwe przy dodatkowej pracy. Ale będzie służyć jako brama do przyjęcia na większą skalę C ++. Za kilka lat ludzie będą narzekać na twój starszy kod, który wygląda tak, jakby został napisany w 1989 roku, ponieważ zamieniają twoje
malloc
wywołanianew
, wydzierają bloki kodu obiektów pętli, aby po prostu wywołać algorytm, i zganić niebezpieczny fałszywy polimorfizm, który mógłby byłyby trywialne, gdyby kompilator mógł to zrobić.Z drugiej strony, wiesz, że byłoby tak samo, gdybyś napisał to w C, więc czy pisanie w C zamiast w C ++ jest kiedykolwiek błędne, biorąc pod uwagę jego istnienie? Jeśli odpowiedź brzmi „nie”, to korzystanie z wybranych funkcji z C ++ również nie może być błędne .
źródło
Wiele języków programowania ma jedną lub więcej kultur, które rozwijają się wokół nich i mają konkretne pomysły na temat tego, jaki język powinien być. Chociaż powinno być możliwe, praktyczne i użyteczne posiadanie języka niskiego poziomu odpowiedniego do programowania systemów, który rozszerza dialekt języka C odpowiedni do tego celu, z pewnymi cechami języka C ++, które również byłyby odpowiednie, kultury otaczające oba języki nie są „ szczególnie faworyzuje takie połączenie.
Czytałem o wbudowanych kompilatorach C, które zawierały niektóre funkcje z C ++, w tym możliwość posiadania
interpretowane zasadniczo jako
a także zdolność do przeciążania funkcji statycznych (problemy ze zmianą nazwy pojawiają się tylko po wyeksportowaniufunkcje są przeciążone). Nie ma powodu, dla którego kompilatory C nie powinny być w stanie obsługiwać takiej funkcjonalności bez nakładania jakichkolwiek dodatkowych wymagań na środowisko łączenia i wykonywania, ale wiele refleksyjnych odrzuceń przez kompilator C prawie wszystkiego od C ++ w celu uniknięcia obciążeń środowiskowych powoduje, że odrzucić nawet funkcje, które nie wiązałyby się z takimi kosztami. Tymczasem kultura C ++ ma niewielkie zainteresowanie jakimkolwiek środowiskiem, które nie obsługuje wszystkich funkcji tego języka. Dopóki kultura C nie zmieni się, aby zaakceptować takie funkcje, lub kultura C ++ zmian, aby zachęcić do implementacji lekkich podzbiorów, kod „hybrydowy” może cierpieć z powodu wielu ograniczeń obu języków, a jego zdolność do czerpania korzyści jest ograniczona. zalety pracy w połączonym nadzbiorze.
Jeśli platforma obsługuje zarówno C, jak i C ++, dodatkowy kod biblioteki wymagany do obsługi C ++ prawdopodobnie spowoduje, że plik wykonywalny będzie nieco większy, choć efekt nie będzie szczególnie dobry. Na wykonanie „funkcji C” w C ++ prawdopodobnie nie będzie to miało szczególnego wpływu. Dużym problemem może być to, w jaki sposób optymalizatory C i C ++ obsługują różne przypadki narożne. Zachowanie typów danych w C jest głównie definiowane przez zawartość przechowywanych w nich bitów, a C ++ ma rozpoznawalną kategorię struktur (PODS - Plain Old Data Structures), których semantyka jest również w większości definiowana przez przechowywane bity. Jednak zarówno standardy C, jak i C ++ czasami pozwalają na zachowanie sprzeczne z tym, jakie sugerują przechowywane bity, a wyjątki od zachowania „przechowywanych bitów” różnią się w obu językach.
źródło
Kolejny ważny czynnik do rozważenia: ktokolwiek odziedziczy twój kod.
Czy ta osoba zawsze będzie programistą C z kompilatorem C? Tak przypuszczam.
Czy ta osoba będzie również programistą C ++ z kompilatorem C ++? Chciałbym być całkiem pewny, zanim mój kod będzie zależny od rzeczy specyficznych dla C ++.
źródło
Polimorfizm to naprawdę dobra funkcja, którą C ++ zapewnia za darmo. Istnieją jednak inne aspekty, które należy rozważyć przy wyborze kompilatora. Istnieje alternatywa dla polimorfizmu w C, ale jego użycie może powodować pewne uniesione brwi. Jest to funkcja variadic, patrz samouczek funkcji variadic .
W twoim przypadku byłoby to coś w rodzaju
Lubię podejście Phillipsa, ale zaśmieca twoją bibliotekę wieloma telefonami. Z powyższym interfejs jest czysty. Ma to swoje wady i ostatecznie jest kwestią wyboru.
źródło
Arg_type_t
ivoid *
wskazuje dane. Powiedziawszy to, tak, podanie (pojedynczej) funkcji argumentu wskazującego, że typ danych jest rzeczywiście realną alternatywą.W konkretnym przypadku statycznego przeciążenia „funkcji” kilku różnych typów, zamiast tego możesz rozważyć użycie C11 z jego maszynerią
_Generic
makr . Myślę, że może to wystarczyć dla twoich ograniczonych potrzeb.Korzystając z odpowiedzi Philipa Kendalla, możesz zdefiniować:
i koduj
transmit(foo)
niezależnie od typufoo
(spośród czterech wymienionych powyżej).Jeśli zależy ci tylko na kompilatorach GCC (i kompatybilnych, np. Clang ), możesz rozważyć
__builtin_types_compatible_p
jegotypeof
rozszerzenie.źródło
Stanowisko IMHO, tak, i muszę zostać schizofrenikiem, aby odpowiedzieć na to pytanie, ponieważ uwielbiam oba języki, ale nie ma to nic wspólnego z wydajnością, ale raczej z bezpieczeństwem i idiomatycznym użyciem języków.
Strona C
Z punktu widzenia C, uważam za tak marnotrawstwo, aby twój kod wymagał C ++ tylko po to, by użyć przeciążenia funkcji. O ile nie używasz go do statycznego polimorfizmu z szablonami C ++, jest to tak trywialny cukier składniowy uzyskany w zamian za przejście na zupełnie inny język. Co więcej, jeśli kiedykolwiek chcesz wyeksportować swoje funkcje do dylib (może to, ale nie musi to być praktyczny problem), nie możesz już tego robić bardzo praktycznie dla powszechnego użytku ze wszystkimi symbolami zmieniającymi nazwy.
Strona C ++
Z punktu widzenia C ++ nie powinieneś używać C ++ jak C z przeciążeniem funkcji. Nie jest to dogmatyzm stylistyczny, ale związany z praktycznym wykorzystaniem codziennego języka C ++.
Twój normalny rodzaj kodu C jest rozsądny i „bezpieczny” do napisania, jeśli pracujesz przeciwko systemowi typu C, który zabrania np. Kopiowania lekarzy
structs
. Gdy pracujesz w znacznie bogatszej systemie typu C ++ 's, codzienne funkcje, które mają ogromną wartość jakmemset
imemcpy
nie stają się funkcje należy oprzeć się na cały czas. Zamiast tego są to funkcje, których na ogół chcesz uniknąć, jak zarazy, ponieważ w typach C ++ nie powinieneś traktować ich jak surowych bitów i bajtów do skopiowania, przetasowania i uwolnienia. Nawet jeśli Twój kod korzysta obecnie z elementów takich jakmemset
prymitywy i POD UDT, w momencie, gdy ktoś doda ctor do dowolnego UDT, którego używasz (w tym tylko dodanie członka, który wymaga takiego, jakstd::unique_ptr
element członkowski) przeciwko takim funkcjom lub funkcji wirtualnej lub cokolwiek innego tego rodzaju, powoduje, że całe twoje normalne kodowanie w stylu C jest podatne na niezdefiniowane zachowanie. Weź to od samego Herb Suttera:Tak wielu programistów C nie zgadza się z tym i słusznie, ponieważ filozofia ma zastosowanie tylko wtedy, gdy piszesz kod w C ++. Najprawdopodobniej są pisania kodu bardzo problematyczne w przypadku korzystania z funkcji takich jak
memcpy
wszechczasów w kodzie, który buduje jak C ++ , ale jest to całkowicie w porządku, jeśli nie to w C . Oba języki są bardzo różne pod tym względem ze względu na różnice w systemie typów. To bardzo kuszące, aby spojrzeć na podzbiór cech, które te dwa mają ze sobą wspólnego i wierzyć, że jeden może być używany podobnie jak drugi, szczególnie po stronie C ++, ale kod C + (lub kod C) jest ogólnie znacznie bardziej problematyczny niż zarówno C, jak i Kod C ++.Podobnie nie powinieneś używać, powiedzmy,
malloc
w kontekście typu C (co oznacza brak EH), jeśli może wywoływać bezpośrednio dowolne funkcje C ++, które mogą rzucać, ponieważ w wyniku tej funkcji masz niejawny punkt wyjścia w funkcji wyjątek, którego nie można skutecznie złapać podczas pisania kodu w stylu C, zanim będzie można uzyskać dostęp dofree
pamięci. Więc gdy masz plik, który buduje jak C ++ z.cpp
rozszerzeniem lub cokolwiek i robi wszystkie te rodzaje rzeczy jakmalloc
,memcpy
,memset
,qsort
itd., to pyta o problemy w dalszej części linii, jeśli jeszcze nie, chyba że jest to szczegół implementacji klasy, która działa tylko z typami pierwotnymi, w którym to momencie nadal musi wykonywać obsługę wyjątków, aby być bezpiecznym dla wyjątków. Jeśli piszesz kod C ++ zamiast tego chcą generalnie polegają na RAII i używać rzeczy jakvector
,unique_ptr
,shared_ptr
itp, i unikać wszelkiego normalnego C-styl kodowania, jeśli to możliwe.Wyjątek - bezpieczeństwo
Ponownie, gdy tylko znajdziesz się w krainie C ++, ludzie będą oczekiwać, że Twój kod będzie bezpieczny. Ludzie mogą zachować Twój kod w przyszłości, biorąc pod uwagę, że jest on już napisany i kompiluje się w C ++, i po prostu używają
std::vector, dynamic_cast, unique_ptr, shared_ptr
itp. W kodzie wywoływanym bezpośrednio lub pośrednio przez Twój kod, uważając go za nieszkodliwy, ponieważ Twój kod jest już „rzekomo” C ++ kod. W tym momencie musimy zmierzyć się z szansą, że wszystko rzuci, a następnie, gdy weźmiesz doskonale doskonały i piękny kod C w ten sposób:... teraz jest zepsuty. Należy go przepisać, aby był bezpieczny:
Obrzydliwy! Dlatego większość programistów C ++ zażąda tego:
Powyżej jest zgodny z RAII kod bezpieczny dla wyjątków, taki jaki generalnie zatwierdziliby programiści C ++, ponieważ funkcja nie wycieknie, która linia kodu wyzwala niejawne wyjście z wyniku
throw
.Wybierz język
Powinieneś albo przyjąć system typów i filozofię C ++ za pomocą RAII, bezpieczeństwo wyjątków, szablony, OOP itp., Albo objąć C, który w dużej mierze opiera się na surowych bitach i bajtach. Nie powinieneś tworzyć bezbożnego małżeństwa między tymi dwoma językami, a zamiast tego rozdzielić je na odrębne języki, aby traktować je bardzo odmiennie, zamiast zamazywać je razem.
Te języki chcą cię poślubić. Zazwyczaj musisz wybrać jedną zamiast randki i wygłupiać się z obiema. Lub możesz być poligamistą jak ja i ożenić się z obojgiem, ale musisz całkowicie zmienić swoje myślenie, spędzając czas ze sobą i utrzymywać ich dobrze oddzielonych od siebie, aby się ze sobą nie walczyli.
Rozmiar binarny
Właśnie z ciekawości próbowałem pobrać moją darmową implementację listy i przeprowadzić test porównawczy i przenieść ją do C ++, ponieważ bardzo mnie to ciekawi:
... i chciałem wiedzieć, czy rozmiar binarny w ogóle się powiększy, po prostu budując jako C ++. Wymagało to ode mnie rozsypywania jawnych rzutów w całym miejscu, co było niezmiernie trudne (jeden z powodów, dla których lubię pisać rzeczy niskiego poziomu, takie jak alokatory i struktury danych w C), ale zajęło mi to tylko minutę.
To było po prostu porównanie 64-bitowej wersji MSVC dla prostej aplikacji konsolowej z kodem, który nie używał żadnych funkcji C ++, nawet przeciążenia operatora - tylko różnica między budowaniem go za pomocą C a używaniem, powiedzmy,
<cstdlib>
zamiast<stdlib.h>
i takie rzeczy, ale byłem zaskoczony, gdy odkryłem, że nie ma to znaczenia dla wielkości binarnej!Plik binarny był
9,728
bajtami, gdy został zbudowany w C, i podobnie9,278
bajty, gdy został skompilowany jako kod C ++. Nie spodziewałem się tego. Myślałem, że takie rzeczy jak EH dodadzą tam trochę (myślałem, że będzie to co najmniej sto bajtów różnych), chociaż prawdopodobnie było w stanie stwierdzić, że nie było potrzeby dodawania instrukcji związanych z EH, ponieważ jestem po prostu używając standardowej biblioteki C i nic nie rzuca. Myślałem cośtak czy inaczej dodałby trochę do rozmiaru binarnego, tak jak RTTI. W każdym razie fajnie było to zobaczyć. Oczywiście nie sądzę, że powinieneś uogólniać na podstawie tego jednego wyniku, ale przynajmniej zrobiło to na mnie wrażenie. Nie miało to również wpływu na testy porównawcze, i oczywiście, ponieważ wyobrażam sobie, że identyczny wynikowy rozmiar binarny oznacza również identyczne wynikowe instrukcje maszynowe.To powiedziawszy, kogo obchodzi rozmiar binarny z wyżej wymienionymi kwestiami bezpieczeństwa i inżynierii? Zatem ponownie wybierz język i zaakceptuj jego filozofię zamiast próbować go przekupić; to polecam.
źródło