Czy złym zwyczajem jest używanie kompilatora C ++ tylko do przeciążania funkcji?

60

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ć printfformatowania stylu z jakimś %sargumentem (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:

  1. 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 ...

  1. 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.

  1. 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 ++).

Podejrzeć
źródło
59
Wiem, że używam C ++ do projektów wykorzystujących tylko funkcje C tylko po to, aby mieć //komentarze. Jeśli to działa, dlaczego nie?
Jules,
76
Zła praktyka polegałaby na ograniczeniu się do C, jeśli dobrze wykorzystujesz funkcje, których nie zapewnia.
Jerry Coffin
35
Kiedy mówisz „użyj kompilatora C ++”, masz na myśli „użyj C ++”. Po prostu to powiedz. Nie możesz skompilować C za pomocą kompilatora C ++, ale możesz łatwo przełączać się z C na C ++, co faktycznie robisz.
user253751,
4
„Ze względu na to, co robię, procesor nie ma dynamicznej alokacji pamięci, a program jest ogólnie dość prosty. Nie wspominając o tym, że urządzenie nie ma prawie mocy ani zasobów procesora. Naprawdę nie ma potrzeby używania jakiegokolwiek C ++”. Mam nadzieję, że pierwsze z tych dwóch zdań powinny być powodem, aby nie używać C ++, ponieważ są one dość złe, jeśli tak jest. C ++ doskonale nadaje się do użycia z systemami osadzonymi.
Pharap
21
@Jules Jestem pewien, że wiesz o tym i zastanawiałem się przez chwilę, ale na wypadek, gdyby ktoś to czytał, nie: //komentarze są w standardzie C od C99.
Davislor,

Odpowiedzi:

77

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:

void transmit_uchar_buffer(unsigned char *buffer);
void transmit_char_buffer(char *buffer);
void transmit_uchar(unsigned char c);
void transmit_char(char c);

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.

Philip Kendall
źródło
8
Powiedziałbym, dlaczego nie, ponieważ przesyłany typ jest (prawdopodobnie) szczegółem implementacji, więc ukrywanie go i pozwalanie kompilatorowi na wybór implementacji może skutkować bardziej czytelnym kodem. A jeśli użycie funkcji C ++ poprawia czytelność, to dlaczego tego nie zrobić?
Jules,
29
Można nawet #definetransmitować () za pomocą C11_Generic
Deduplicator
16
@Jules Ponieważ jest to bardzo mylące, jakie funkcje C ++ mogą być używane w projekcie. Czy potencjalna zmiana zawierająca obiekty zostanie odrzucona? Co z szablonami? Komentarze w stylu C ++? Jasne, możesz obejść ten problem z dokumentem w stylu kodowania, ale jeśli wszystko, co robisz, to zwykły przypadek przeciążenia funkcji, to po prostu napisz C.
Philip Kendall,
25
@Phillip „Komentarze w stylu C ++” są ważne od ponad dekady.
David Conrad,
12
@Jules: podczas gdy przesyłany typ jest prawdopodobnie szczegółową implementacją oprogramowania, które dokonuje wyceny ubezpieczenia, aplikacja OP wydaje się być systemem osadzonym wykonującym komunikację szeregową, w którym typ i wielkość danych ma istotne znaczenie.
whatsisname
55

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 classzmieniono nazwy na klasslub kclass, 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.

Jörg W Mittag
źródło
15

Obiektywna odpowiedź na twój dylemat będzie zależeć od:

  1. Czy rozmiar pliku wykonywalnego znacznie wzrośnie, jeśli użyjesz C ++.
  2. Czy istnieje zauważalny negatywny wpływ na wydajność, jeśli używasz C ++.
  3. Określa, czy kod może być ponownie użyty na innej platformie, na której dostępny jest tylko kompilator C.

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 Sahu
źródło
9
Muszę powiedzieć, że nigdy nie spotkałem kompilatora C ++, który generuje znacznie gorszy kod niż kompilator C dla kodu napisanego we wspólnym podzbiorze dwóch języków. Kompilatory C ++ mają złe wyniki pod względem rozmiaru i wydajności, ale z mojego doświadczenia wynika, że ​​zawsze niewłaściwe jest korzystanie z funkcji C ++, które spowodowało problem ... W szczególności, jeśli martwisz się rozmiarem, nie używaj iostreams i nie używaj szablony, ale poza tym wszystko powinno być w porządku.
Jules,
@Jules: Właśnie za to, co jest warte (niewiele, IMO) Widziałem, co zostało sprzedane jako pojedynczy kompilator dla C i C ++ (Turbo C ++ 1.0, jeśli pamięć służy), wytwarzają znacząco różne wyniki dla identycznych danych wejściowych. Jednak, jak rozumiem, działo się to przed ukończeniem własnego kompilatora C ++, więc chociaż wyglądał on jak jeden kompilator na zewnątrz, tak naprawdę miał dwa całkowicie osobne kompilatory - jeden dla C, drugi dla C ++.
Jerry Coffin,
1
@JerryCoffin Jeśli pamięć służy, produkty Turbo nigdy nie cieszyły się dobrą reputacją. A jeśli była to wersja 1.0, możesz być usprawiedliwiony, że nie jesteś bardzo wyrafinowany. Prawdopodobnie nie jest to zbyt reprezentatywne.
Barmar,
10
@Barmar: W rzeczywistości mieli dość dobrą reputację od dłuższego czasu. Ich zła reputacja wynika obecnie głównie z ich wieku. Są konkurencyjne w stosunku do innych kompilatorów tego samego rocznika - ale nikt nie zadaje pytań o to, jak robić rzeczy z gcc 1.4 (lub czymkolwiek). Ale masz rację - to nie jest zbyt reprezentatywne.
Jerry Coffin
1
@Jules Twierdzę, że nawet szablony są w porządku. Pomimo popularnej teorii tworzenie instancji szablonu nie powoduje niejawnego zwiększenia rozmiaru kodu, rozmiar kodu na ogół nie wzrośnie, dopóki nie zostanie użyta funkcja z szablonu (w takim przypadku wzrost rozmiaru będzie proporcjonalny do rozmiaru funkcji) lub chyba, że ​​szablon jest deklarowanie zmiennych statycznych. Nadal obowiązują zasady wbudowania, więc możliwe jest pisanie szablonów, w których kompilator wstawia wszystkie funkcje.
Pharap
13

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,

  1. Czy rozmiar pliku wykonywalnego znacznie wzrośnie, jeśli użyjesz C ++.

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.

  1. Czy istnieje zauważalny negatywny wpływ na wydajność, jeśli używasz C ++.

Wątpię, czy zauważysz zauważalną różnicę w wydajności opisanego kodu w porównaniu z hipotetycznym analogiem czystego C.

  1. Określa, czy kod może być ponownie użyty na innej platformie, na której dostępny jest tylko kompilator 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?

John Bollinger
źródło
4
„Plik wykonywalny może być nieco większy po skompilowaniu jako C ++ ... do tego stopnia, że ​​wszystkie symbole są zachowane w pliku wykonywalnym.” Jednak, żeby być uczciwym, każdy przyzwoity optymalizujący łańcuch narzędzi powinien mieć opcję linkera, aby usunąć te symbole w kompilacjach „release”, co oznacza, że ​​rozpętałyby jedynie kompilacje debugujące. Nie sądzę, że to poważna strata. W rzeczywistości jest to częściej korzyść.
Cody Gray
5

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ą overloadsł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 mallocw kodzie C będzie błędem niedopasowania typu - w twoim przypadku nie będzie problemu bez pamięci dynamicznej! Podobnie, wszystkie twoje voidwskaź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 mallocwywołania new, 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 .

JDługosz
źródło
2

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

foo.someFunction(a,b,c);

interpretowane zasadniczo jako

typeOfFoo_someFunction(foo, a, b, c);

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.

supercat
źródło
1
Interesująca opinia, ale nie odnosi się do pytania PO.
R Sahu,
@RSahu: Kod, który pasuje do „kultury” otaczającej język, może być lepiej obsługiwany i łatwiejszy do dostosowania do różnych implementacji niż kod, który tego nie robi. Zarówno kultury C, jak i C ++ są niechętne do tego, jaki sposób użytkowania sugeruje OP. Dodam kilka bardziej szczegółowych punktów w odniesieniu do konkretnych pytań.
supercat
1
Zauważ, że w komitecie standardowym C ++ znajduje się grupa osadzona / finansowa / związana z grami, której celem jest rozwiązanie dokładnie tego rodzaju sytuacji „małego zainteresowania”, które według ciebie są odrzucane.
Jak
@Yakk: Przyznaję, że nie śledziłem strasznie świata C ++, ponieważ moje zainteresowania dotyczą głównie C; Wiem, że podjęto wysiłki w kierunku wprowadzenia bardziej wbudowanych dialektów, ale nie sądziłem, że tak naprawdę doszli.
supercat
2

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 ++.

reinierpost
źródło
2

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

enum serialDataTypes
{
 INT =0,
 INT_P =1,
 ....
 }Arg_type_t;
....
....
void transmit(Arg_type_t serial_data_type, ...)
{
  va_list args;
  va_start(args, serial_data_type);

  switch(serial_data_type)
  {
    case INT: 
    //Send integer
    break;

    case INT_P:
    //Send data at integer pointer
    break;
    ...
   }
 va_end(args);
}

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.

nikhil_kotian
źródło
Funkcje variadic służą przede wszystkim do przypadków użycia, w których zarówno liczba argumentów, jak i ich typy są zmienne. W przeciwnym razie nie potrzebujesz go, a w szczególności jest to przesada w przypadku twojego konkretnego przykładu. Łatwiej byłoby użyć zwykłej funkcji, która akceptuje Arg_type_ti void *wskazuje dane. Powiedziawszy to, tak, podanie (pojedynczej) funkcji argumentu wskazującego, że typ danych jest rzeczywiście realną alternatywą.
John Bollinger
1

W konkretnym przypadku statycznego przeciążenia „funkcji” kilku różnych typów, zamiast tego możesz rozważyć użycie C11 z jego maszynerią _Genericmakr . Myślę, że może to wystarczyć dla twoich ograniczonych potrzeb.

Korzystając z odpowiedzi Philipa Kendalla, możesz zdefiniować:

#define transmit(X) _Generic((X),      \
   unsigned char*: transmit_uchar_buffer, \
   char*: transmit_char_buffer,           \
   unsigned char: transmit_uchar,         \
   char: transmit_char) ((X))

i koduj transmit(foo) niezależnie od typu foo(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_pjego typeofrozszerzenie.

Basile Starynkevitch
źródło
1

Czy złym zwyczajem jest używanie kompilatora C ++ tylko do przeciążania funkcji?

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ść jak memseti memcpynie 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 jak memsetprymitywy 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_ptrelement 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:

memcpyi memcmpnaruszają system typów. Korzystanie memcpyskopiować obiektów jest jak zarabiać za pomocą kserokopiarki. Używanie memcmpdo porównywania obiektów jest jak porównywanie lampartów poprzez liczenie ich miejsc. Narzędzia i metody mogą wydawać się do wykonania zadania, ale są zbyt szorstkie, aby zrobić to w sposób akceptowalny. W obiektach C ++ chodzi o ukrywanie informacji (prawdopodobnie najbardziej dochodowa zasada w inżynierii oprogramowania; patrz pozycja 11): Obiekty ukrywają dane (patrz pozycja 41) i opracowują precyzyjne abstrakcje do kopiowania tych danych przez konstruktory i operatory przypisania (patrz pozycje 52 do 55) . Przełamywanie tego wszystkiego memcpyjest poważnym naruszeniem ukrywania informacji i często prowadzi do wycieków pamięci i zasobów (w najlepszym przypadku), awarii (gorszych) lub nieokreślonego zachowania (najgorszych) - Standardy kodowania C ++.

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 pisania kodu bardzo problematyczne w przypadku korzystania z funkcji takich jak memcpywszechczasó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, mallocw 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 do freepamięci. Więc gdy masz plik, który buduje jak C ++ z .cpprozszerzeniem lub cokolwiek i robi wszystkie te rodzaje rzeczy jak malloc, memcpy, memset,qsortitd., 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 jak vector, unique_ptr, shared_ptritp, i unikać wszelkiego normalnego C-styl kodowania, jeśli to możliwe.

Powodem, dla którego możesz grać żyletkami w typach danych C i rentgenowskich oraz bawić się ich bitami i bajtami bez skłonności do powodowania ubocznych obrażeń w zespole (chociaż nadal możesz naprawdę zranić siebie w obu przypadkach), nie jest to, co C typy mogą zrobić, ale z powodu tego, czego nigdy nie będą w stanie zrobić. W momencie, gdy rozszerzysz system typów C o funkcje C ++, takie jak ctors, dtors i vtables, wraz z obsługą wyjątków, cały idiomatyczny kod C byłby znacznie bardziej niebezpieczny niż obecnie, a zobaczysz nowy rodzaj ewolucja filozofii i sposobu myślenia, która zachęci do zupełnie innego stylu kodowania, jak widać w C ++, który obecnie rozważa nawet użycie surowego wskaźnika nadużycia dla klasy zarządzającej pamięcią w przeciwieństwie do, powiedzmy, zasobu zgodnego z RAII unique_ptr. Ten sposób myślenia nie wyewoluował z absolutnego poczucia bezpieczeństwa. Wyewoluowało z tego, co C ++ musi szczególnie chronić przed funkcjami takimi jak obsługa wyjątków, biorąc pod uwagę to, na co pozwala jedynie poprzez swój system typów.

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_ptritp. 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:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        f(n, x); // some function which, now being a C++ function, may 
                 // throw today or in the future.
        ...
        free(x);
        return success;
    }
    return fail;
}

... teraz jest zepsuty. Należy go przepisać, aby był bezpieczny:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        try
        {
            f(n, x); // some function which, now being a C++ function, may 
                     // throw today or in the future (maybe someone used
                     // std::vector inside of it).
        }
        catch (...)
        {
            free(x);
            throw;
        }
        ...
        free(x);
        return success;
    }
    return fail;
}

Obrzydliwy! Dlatego większość programistów C ++ zażąda tego:

void some_func(int n, ...)
{
    vector<int> x(n);
    f(x); // some function which, now being a C++ function, may throw today
          // or in the future.
}

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:

[...] nie wiem, jak by to wyglądało dla C, ponieważ nie korzystałem z kompilatora C.

... 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,728bajtami, gdy został zbudowany w C, i podobnie 9,278bajty, 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