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 memset
i memcpy
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 jak memset
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:
memcpy
i memcmp
naruszają system typów. Korzystanie memcpy
skopiować obiektów jest jak zarabiać za pomocą kserokopiarki. Używanie memcmp
do 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 memcpy
jest 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 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 do free
pamięci. Więc gdy masz plik, który buduje jak C ++ z .cpp
rozszerzeniem lub cokolwiek i robi wszystkie te rodzaje rzeczy jak malloc
, 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 jak vector
, unique_ptr
, shared_ptr
itp, 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_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:
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,728
bajtami, gdy został zbudowany w C, i podobnie 9,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.
//
komentarze. Jeśli to działa, dlaczego nie?