Dowiedz się, jak Bjarne Stroustrup ukształtował C++ wokół abstrakcji zerokosztowych i dlaczego oprogramowanie krytyczne dla wydajności nadal opiera się na kontroli, narzędziach i ekosystemie C++.

C++ powstało z konkretną obietnicą: powinieneś móc pisać ekspresyjny, wysokopoziomowy kod — klasy, kontenery, generyczne algorytmy — bez automatycznego naliczania dodatkowych kosztów w czasie wykonania za tę ekspresję. Jeśli nie korzystasz z cechy, nie powinieneś za nią płacić. Jeśli z niej korzystasz, koszt powinien być zbliżony do tego, co napisałbyś ręcznie w stylu niskopoziomowym.
Ten artykuł opowiada, jak Bjarne Stroustrup ukształtował ten cel w język i dlaczego idea wciąż ma znaczenie. To także praktyczny przewodnik dla każdego, komu zależy na wydajności i kto chce zrozumieć, co C++ stara się optymalizować — poza sloganami.
„Wysoka wydajność” to nie tylko podniesienie liczby w benchmarku. W praktyce zwykle oznacza przynajmniej jedno z następujących ograniczeń:
Gdy te ograniczenia są ważne, ukryty narzut — dodatkowe alokacje, niepotrzebne kopiowanie lub wirtualny dispatch tam, gdzie niepotrzebny — może decydować między „działa” a „nie trafia w cel”.
C++ często wybiera się do programowania systemowego i części krytycznych wydajnościowo: silniki gier, przeglądarki, bazy danych, potoki grafiki, systemy tradingowe, robotyka, telekomunikacja i fragmenty systemów operacyjnych. To nie jedyna opcja — wiele nowoczesnych produktów łączy języki. Ale C++ pozostaje częstym narzędziem „wewnętrznej pętli”, gdy zespoły potrzebują bezpośredniej kontroli nad mapowaniem kodu na maszynę.
Dalej rozbijemy ideę abstrakcji zerokosztowych na prosty język, połączymy ją ze specyficznymi technikami C++ (jak RAII i szablony) oraz opiszemy rzeczywiste kompromisy, z jakimi mierzą się zespoły.
Bjarne Stroustrup nie tworzył „nowego języka” dla samego tworzenia. Pod koniec lat 70. i na początku 80. pracował przy systemach, gdzie C był szybki i bliski maszynie, ale większe programy trudno było organizować, modyfikować i łatwo w nich popełnić błędy.
Jego cel był prosty do sformułowania, lecz trudny do osiągnięcia: wprowadzić lepsze sposoby organizacji dużych programów — typy, moduły, enkapsulację — bez rezygnowania z wydajności i dostępu do sprzętu, które sprawiały, że C był wartościowy.
Pierwszy krok dosłownie nosił nazwę „C with Classes.” Ta nazwa sugeruje kierunek: nie przebudowę od zera, lecz ewolucję. Zachowaj to, co C robiło dobrze (przewidywalna wydajność, bezpośredni dostęp do pamięci, proste konwencje wywołań), a dodaj brakujące narzędzia do budowania dużych systemów.
W miarę rozwoju C++ dodatki nie były tylko „większą liczbą funkcji”. Miały na celu to, aby wysokopoziomowy kod kompilował się do podobnego kodu maszynowego, jaki napisałbyś ręcznie w C, jeśli jest używany rozsądnie.
Główne napięcie Stroustrupa było — i nadal jest — między:
Wiele języków wybiera stronę ukrywając szczegóły (co może ukrywać narzut). C++ stara się pozwolić budować abstrakcje, jednocześnie dając możliwość odpowiedzenia na pytanie: „Ile to kosztuje?” i — gdy trzeba — zejścia na niski poziom.
Ta motywacja — abstrakcja bez kary — łączy wątek od wczesnego wsparcia klas po późniejsze pomysły takie jak RAII, szablony i STL.
„Abstrakcje zerokosztowe” brzmią jak slogan, ale to obietnica dotycząca kompromisów. W codziennej wersji:
Jeśli nie używasz funkcji, nie płacisz za nią. A jeśli jej używasz, powinieneś zapłacić mniej więcej tyle, ile zapłaciłbyś, gdybyś napisał niskopoziomowy kod ręcznie.
W terminach wydajności „koszt” to wszystko, co sprawia, że program wykonuje dodatkową pracę w czasie działania. To mogą być:
Abstrakcje zerokosztowe pozwalają pisać czytelny, wysokopoziomowy kod — typy, klasy, funkcje, generyczne algorytmy — a jednocześnie wytwarzać kod maszynowy równie bezpośredni jak ręczne pętle i manualne zarządzanie zasobami.
C++ nie sprawi, że wszystko będzie szybkie jak z automatu. Umożliwia napisanie wysokopoziomowego kodu, który kompiluje się do efektywnych instrukcji — ale nadal możesz wybrać kosztowne wzorce.\n\nJeśli alokujesz w gorącej pętli, kopiujesz duże obiekty wielokrotnie, pomijasz układy przyjazne cache lub budujesz warstwy pośrednictwa blokujące optymalizacje, program spowolni. C++ tego nie zablokuje. Cel „zerokosztowej abstrakcji” dotyczy unikania wymuszonego narzutu, a nie gwarantowania, że wszystkie decyzje będą dobre.
W pozostałej części artykułu zobaczymy, jak kompilatory „kasują” narzut abstrakcji, dlaczego RAII może być jednocześnie bezpieczniejsze i szybsze, jak szablony generują kod działający jak ręcznie dopracowane wersje oraz jak STL dostarcza wielokrotnego użytku elementy bez ukrytej pracy — jeśli są używane ostrożnie.
C++ opiera się na prostej umowie: płać więcej podczas kompilacji, aby płacić mniej w czasie wykonywania. Gdy kompilujesz, kompilator nie tylko tłumaczy kod — stara się usunąć narzut, który w innym wypadku pojawiłby się podczas działania programu.
W czasie kompilacji kompilator może „przedpłacić” wiele wydatków:
Celem jest to, aby twoja czytelna struktura zamieniła się w kod maszynowy zbliżony do tego, który napisałbyś ręcznie.
Mała funkcja pomocnicza jak:
int add_tax(int price) { return price * 108 / 100; }
często po kompilacji nie będzie w ogóle wywołaniem funkcji. Zamiast „skok do funkcji, ustawienie argumentów, return” kompilator może wkleić arytmetykę bezpośrednio tam, gdzie funkcja była użyta. Abstrakcja (ładnie nazwana funkcja) w praktyce znika.
Pętle też są optymalizowane. Prosta pętla po ciągłym zakresie może zostać przekształcona: sprawdzenia granic usunięte, powtarzane obliczenia wyniesione poza pętlę, ciało pętli przearanżowane, aby lepiej wykorzystać CPU.
To praktyczne znaczenie abstrakcji zerokosztowych: dostajesz czytelniejszy kod bez stałego kosztu runtime za strukturę, którą go użyłeś.
Nic nie jest za darmo. Mocniejsze optymalizacje i „znikające abstrakcje” mogą oznaczać dłuższy czas kompilacji i czasem większe binaria (np. gdy wiele miejsc wywołania zostanie zinline’owanych). C++ daje wybór — i odpowiedzialność — by wyważyć koszt budowy względem szybkości działania.
RAII (Resource Acquisition Is Initialization) to prosta zasada o dużych konsekwencjach: czas życia zasobu jest powiązany z zakresem. Gdy obiekt powstaje, przejmuje zasób. Gdy obiekt wychodzi ze zasięgu, jego destruktor go zwalnia — automatycznie.
Zasobem może być prawie wszystko, co trzeba niezawodnie posprzątać: pamięć, pliki, mutexy, uchwyty do baz danych, gniazda, bufory GPU i więcej. Zamiast pamiętać o wywoływaniu close(), unlock() czy free() na każdej ścieżce, umieszczasz sprzątanie w jednym miejscu (destruktorze) i pozwalasz językowi zagwarantować jego wykonanie.
Ręczne sprzątanie zwykle rodzi „cieniowy” kod: dodatkowe sprawdzenia if, powielone obsługi return i ostrożne wywołania sprzątania po każdej możliwej ścieżce błędu. Łatwo pominąć gałąź, szczególnie gdy funkcje ewoluują.
RAII zazwyczaj generuje kod prostoliniowy: przejmij, wykonaj pracę, i pozwól wyjściu ze zasięgu zająć się sprzątaniem. To zmniejsza zarówno liczbę błędów (wycieki, podwójne zwolnienia, nieodblokowane mutexy), jak i narzut runtime związany z defensywną obsługą. W kategoriach wydajności mniej gałęzi obsługi błędów w gorącej ścieżce może oznaczać lepsze zachowanie cache instrukcji i mniej błędnych przewidywań gałęzi.
Wycieki i nierozwiązane locki to nie tylko problemy poprawnościowe; to bomby zegarowe dla wydajności. RAII sprawia, że zwalnianie zasobów jest przewidywalne, co pomaga systemom zachować stabilność pod obciążeniem.
RAII błyszczy przy wyjątkach, bo podczas rozwijania stosu destruktory są wywoływane, więc zasoby są zwalniane nawet przy nagłych skokach sterowania. Wyjątki to narzędzie: ich koszt zależy od sposobu użycia i ustawień kompilatora/platformy. Kluczowe jest to, że RAII sprawia, iż sprzątanie jest deterministyczne niezależnie od sposobu opuszczenia zasięgu.
Szablony często opisuje się jako „generowanie kodu w czasie kompilacji” i to dobre przybliżenie. Piszesz algorytm raz — np. „posortuj te elementy” lub „przechowuj elementy w kontenerze” — a kompilator produkuje wersję dopasowaną do konkretnych typów, których użyjesz.
Ponieważ kompilator zna konkretne typy, może inlinować funkcje, wybrać odpowiednie operacje i agresywnie optymalizować. W wielu przypadkach oznacza to uniknięcie wywołań wirtualnych, sprawdzeń typów w runtime i dynamicznego dispatchu, które inaczej byłyby potrzebne do realizacji generyczności.
Na przykład szablon max(a, b) dla całkowitych może stać się kilkoma instrukcjami maszynowymi. Ten sam szablon użyty z małą strukturą również może skompilować się do bezpośrednich porównań i kopiowań — bez wskaźników do interfejsów i bez „jaki to typ?” w czasie wykonywania.
Biblioteka standardowa opiera się mocno na szablonach, bo pozwalają na wielokrotne użycie elementów bez ukrytej pracy:
std::vector<T> i std::array<T, N> przechowują twój T bezpośrednio.std::sort działają na wielu typach, o ile można je porównywać.Efekt to kod, który często działa jak ręcznie napisany, specyficzny dla typu — bo w istocie nim się staje.
Szablony nie są bez kosztów dla deweloperów. Mogą wydłużać czas kompilacji (więcej kodu do wygenerowania i zoptymalizowania), a gdy coś pójdzie nie tak, komunikaty o błędach bywają długie i trudne do odczytania. Zespoły zwykle radzą sobie dzięki wytycznym, dobrym narzędziom i ograniczaniu złożoności szablonów tam, gdzie to się opłaca.
Standard Template Library (STL) to wbudowane narzędzie C++ do pisania wielokrotnego użytku kodu, który nadal może kompilować się do zwartego kodu maszynowego. To nie osobny framework, który „dobudowujesz” — to część biblioteki standardowej, zaprojektowana wokół idei zerokosztowej: używaj wysokopoziomowych elementów bez płacenia za pracę, której nie zażądałeś.
vector, string, array, map, unordered_map, list i inne.sort, find, count, transform, accumulate itd.To rozdzielenie ma znaczenie. Zamiast każdemu kontenerowi wymyślać „sort” czy „find”, STL daje ci jedną, przetestowaną pulę algorytmów, które kompilator może agresywnie zoptymalizować.
Kod STL może być szybki, ponieważ wiele decyzji zapada na etapie kompilacji. Jeśli sortujesz vector<int>, kompilator zna typ elementów i typ iteratora, i może inline’ować porównania oraz optymalizować pętle jak kod ręczny. Kluczem jest dobór struktur danych dopasowanych do wzorców dostępu.
vector vs list: vector często jest domyślnym wyborem, bo elementy są ciągłe w pamięci, co sprzyja cache’owi i jest szybkie przy iteracji i dostępie losowym. list przydaje się, gdy naprawdę potrzebujesz stabilnych iteratorów i częstych splicingów/wstawek w środku bez przesuwania elementów — ale kosztuje narzut na węzeł i jest wolniejsza do przeglądania.unordered_map vs map: unordered_map zwykle dobrze się sprawdza przy szybkich, średnich czasowo wyszukiwaniach kluczy. map zachowuje kolejność kluczy, co jest przydatne do zapytań zakresowych (np. „wszystkie klucze między A i B”) i przewidywalnej iteracji, ale wyszukiwania są zwykle wolniejsze niż w dobrej tablicy mieszającej.Dla głębszego przewodnika zobacz: /blog/choosing-cpp-containers
Nowoczesne C++ nie porzuciło pierwotnej idei Stroustrupa „abstrakcja bez kary”. Wiele nowszych cech skupia się na tym, by pisać czytelny kod, dając jednocześnie kompilatorowi szansę na wytworzenie zwartego kodu maszynowego.
Częstym źródłem spowolnienia jest niepotrzebne kopiowanie — duże stringi, bufory czy struktury duplikowane tylko po to, by przekazać dalej. Semantyka przenoszenia to prosty pomysł: „nie kopiuj, jeśli po prostu przekazujesz własność”. Gdy obiekt jest tymczasowy (lub przestajesz go używać), C++ może przenieść jego wnętrze do nowego właściciela zamiast go duplikować. W praktyce oznacza to mniej alokacji, mniej ruchu pamięci i szybsze wykonanie — bez ręcznego mikrozarządzania bajtami.
constexpr: obliczaj wcześniej, by runtime robił mniejNiektóre wartości i decyzje nigdy się nie zmieniają (rozmiary tabel, stałe konfiguracyjne, tablice wyszukiwania). Dzięki constexpr możesz poprosić C++, aby obliczył rezultaty wcześniej — podczas kompilacji — tak by działający program robił mniej pracy.
Zysk to zarówno prędkość, jak i prostota: kod może wyglądać jak normalne obliczenie, a wynik może trafić „na stałe” do programu.
Ranges (i powiązane widoki) pozwalają wyrazić „weź te elementy, odfiltruj, przetransformuj” w czytelny sposób. Użyte rozsądnie, mogą skompilować się do prostych pętli — bez wymuszania dodatkowych warstw w runtime.
Te cechy wspierają kierunek zerokosztowy, ale wydajność nadal zależy od użycia i od zdolności kompilatora do optymalizacji końcowego programu. Czysty, wysokopoziomowy kod często pięknie się optymalizuje — ale warto mierzyć, gdy prędkość naprawdę się liczy.
C++ potrafi skompilować „wysokopoziomowy” kod do bardzo szybkich instrukcji maszynowych — ale nie gwarantuje wydajnych wyników domyślnie. Wydajność zwykle nie ginie dlatego, że użyłeś szablonu czy czytelnej abstrakcji. Traci się ją, gdy drobne koszty wkradają się do gorących ścieżek i mnożą miliony razy.
Kilka wzorców pojawia się regularnie:
Żaden z tych problemów nie jest „problematyczny dla C++” sam w sobie. To problemy projektowe i użycia — mogą występować w każdym języku. Różnica jest taka, że C++ daje wystarczającą kontrolę, aby je naprawić, ale też wystarczająco dużo swobody, by je popełnić.
Zacznij od nawyków upraszczających model kosztów:
reserve(), unikaj tworzenia tymczasowych kontenerów w pętlach wewnętrznych.Używaj profilera, który odpowie na podstawowe pytania: Gdzie spędzany jest czas? Ile alokacji zachodzi? Jakie funkcje są wywoływane najczęściej? Połącz to z lekkimi benchmarkami dla najważniejszych części.
Regularnie praktykowane, „abstrakcje zerokosztowe” stają się praktyczne: zachowujesz czytelny kod, a potem usuwasz specyficzne koszty wykryte przez pomiary.
C++ pojawia się tam, gdzie milisekundy (lub mikrosekundy) to nie „miłe mieć”, lecz wymóg produktowy. Często stoi za: niskolatencyjnymi systemami tradingowymi, silnikami gier, komponentami przeglądarek, silnikami baz danych i magazynów, firmware wbudowanym, oraz obliczeniami wysokiej wydajności (HPC). To nie wszystkie zastosowania, ale dobre przykłady dlaczego język się utrzymuje.
Wiele dziedzin wrażliwych na wydajność bardziej ceni przewidywalność niż maksymalną przepustowość: wartości skrajne latencji powodujące spadki klatek, zrywy audio, utracone okazje rynkowe czy przekroczenia real-time deadlineów. C++ pozwala zespołom decydować, kiedy pamięć jest alokowana, kiedy zwalniana i jak dane są ułożone w pamięci — wybory, które silnie wpływają na zachowanie cache i skoki latencji.
Ponieważ abstrakcje mogą się kompilować do prostego kodu maszynowego, kod C++ można projektować dla utrzymania czytelności bez automatycznego płacenia runtime za tę strukturę. Gdy ponosisz koszty (dynamiczna alokacja, dispatch wirtualny, synchronizacja), zwykle są one widoczne i mierzalne.
Pragmatyczny powód popularności C++ to interoperacyjność. Wiele organizacji ma dekady bibliotek w C, interfejsów systemowych, SDK urządzeń i sprawdzonego kodu, którego nie da się łatwo przepisać. C++ może wywoływać API C bezpośrednio, udostępniać interfejsy zgodne z C i stopniowo modernizować fragmenty kodu, bez konieczności migracji „wszystko na raz”.
W programowaniu systemowym i embedded „blisko sprzętu” nadal ma znaczenie: bezpośredni dostęp do instrukcji, SIMD, pamięci mapowanej do urządzeń i optymalizacji specyficznych dla platformy. W połączeniu z dojrzałymi kompilatorami i narzędziami profilującymi, C++ jest często wybierany, gdy zespoły muszą wycisnąć wydajność zachowując kontrolę nad binarkami, zależnościami i zachowaniem w runtime.
C++ zdobywa lojalność, bo może być ekstremalnie szybkie i elastyczne — ale ta moc ma koszt. Krytyka nie jest wyimaginowana: język jest duży, stare bazy kodu niosą ryzykowne nawyki, a błędy mogą prowadzić do crashy, korupcji danych czy luk bezpieczeństwa.
C++ rozwijało się przez dekady i to widać. Zobaczysz wiele sposobów na osiągnięcie tego samego celu i „ostre krawędzie”, które karzą drobne błędy. Dwa częste problemy:
Starsze wzorce zwiększają ryzyko: surowe new/delete, ręczne zarządzanie pamięcią i niekontrolowana arytmetyka wskaźników są ciągle obecne w legacy kodzie.
Nowoczesna praktyka C++ polega na czerpaniu korzyści przy jednoczesnym ograniczaniu „strzałów w stopę”. Zespoły robią to, przyjmując wytyczne i bezpieczne podzbiory języka — nie jako obietnicę doskonałego bezpieczeństwa, ale jako praktyczny sposób na ograniczenie trybów awarii.
Typowe kroki:
std::vector, std::string) zamiast ręcznych alokacji.std::unique_ptr, std::shared_ptr) do jawnego zarządzania własnością.clang-tidy.Standard wciąż zmierza w stronę bezpieczniejszego i czytelniejszego kodu: lepsze biblioteki, bardziej wyraziste typy i praca nad kontraktami, wskazówkami bezpieczeństwa i wsparciem narzędzi. Kompromis pozostaje: C++ daje wpływ, ale zespoły muszą wypracować niezawodność przez dyscyplinę, przeglądy, testy i nowoczesne konwencje.
C++ to dobry wybór, gdy potrzebujesz precyzyjnej kontroli nad wydajnością i zasobami i możesz zainwestować w dyscyplinę. Chodzi mniej o „C++ jest szybsze”, a bardziej o „C++ pozwala zdecydować, jaka praca jest wykonywana, kiedy i za jaki koszt”.
Wybierz C++ gdy większość z poniższych jest prawdziwa:
Rozważ inny język gdy:
Jeśli wybierasz C++, ustal zabezpieczenia wcześnie:
new/delete, stosuj std::unique_ptr/std::shared_ptr świadomie i zabroń niekontrolowanej arytmetyki wskaźników w kodzie aplikacji.Jeśli oceniasz opcje lub planujesz migrację, warto też zachować wewnętrzne notatki decyzyjne i udostępniać je w przestrzeni zespołowej jak /blog dla przyszłych rekrutów i interesariuszy.
Nawet jeśli krytyczne wydajnościowo jądro pozostaje w C++, wiele zespołów musi szybko dostarczać kod otaczający produkt: pulpity, narzędzia administracyjne, wewnętrzne API czy prototypy pozwalające zweryfikować wymagania przed zaangażowaniem się w implementację niskopoziomową.
W tym miejscu Koder.ai może być praktycznym uzupełnieniem. To platforma vibe-coding, która pozwala budować aplikacje webowe, serwerowe i mobilne z interfejsu czatu (React w webie, Go + PostgreSQL w backendzie, Flutter na mobile), z opcjami takimi jak tryb planowania, eksport źródeł, wdrożenie/hosting, własne domeny oraz snapshoty z możliwością rollbacku. Innymi słowy: możesz szybko iterować nad „wszystkim wokół gorącej ścieżki”, trzymając komponenty C++ skupione na częściach, gdzie abstrakcje zerokosztowe i pełna kontrola mają największe znaczenie.
„Abstrakcja zerokosztowa” to cel projektowy: jeśli nie używasz danej funkcji, nie powinna ona dodawać narzutu w czasie wykonania, a jeśli ją używasz, wygenerowany kod maszynowy powinien być zbliżony do tego, co napisałbyś ręcznie w niższym poziomie abstrakcji.
Praktycznie oznacza to, że możesz pisać czytelniejszy kod (typy, funkcje, algorytmy generyczne) bez automatycznych dodatkowych alokacji, pośrednictw czy kosztownego dispatchu.
W tym kontekście „koszt” to dodatkowa praca w czasie wykonywania, taka jak:
Celem jest, by te koszty były widoczne i by język nie zmuszał do ich ponoszenia bez potrzeby.
Działa najlepiej, gdy kompilator może „przejrzeć” abstrakcję w czasie kompilacji — typowe przypadki to małe funkcje, które są inlinowane, stałe obliczane w czasie kompilacji (constexpr) oraz szablony z konkretnymi typami.
Mniej skuteczne jest to, gdy dominują pośrednictwa w czasie wykonywania (np. intensywny dispatch wirtualny w gorącej pętli) lub gdy wprowadzasz częste alokacje i struktury powodujące śledzenie wskaźników.
C++ przesuwa wiele kosztów na etap kompilacji, by runtime był możliwie lekki. Typowe przykłady:
Aby to wykorzystać, kompiluj z optymalizacjami (np. -O2/-O3) i pisz kod tak, by kompilator mógł go poprawnie analizować.
RAII (Resource Acquisition Is Initialization) wiąże czas życia zasobu ze zasięgiem: przy konstrukcji obiekt przejmuje zasób, a w destruktorze go zwalnia. Używaj jej dla pamięci, plików, mutexów, socketów itd.
Praktyczne nawyki:
std::vector, std::string).RAII jest szczególnie cenne przy wyjątkach, ponieważ podczas rozwijania stosu destruktory nadal są wywoływane, więc zasoby są zwalniane.
Jeśli chodzi o wydajność, wyjątki kosztują zwykle wtedy, gdy są rzucane, a nie wtedy, gdy są możliwe. Jeśli gorąca ścieżka często rzuca wyjątki, warto rozważyć kody błędów lub typy podobne do expected; jeśli rzuty są wyjątkowe, RAII + wyjątki często zachowują prostotę i wydajność ścieżki krytycznej.
Szablony pozwalają napisać ogólny algorytm, który na etapie kompilacji staje się wersją dopasowaną do konkretnych typów. Dzięki temu często można uniknąć wywołań wirtualnych i sprawdzeń typów w czasie wykonywania, a kompilator może wszystko inlinować i zoptymalizować.
Koszty do zaplanowania:
Trzymaj złożoność szablonów tam, gdzie przynosi korzyść (algorytmy, komponenty wielokrotnego użytku), unikaj nadmiernego templatingu w warstwach przyklejających aplikację.
Domyślnie wybieraj std::vector dla ciągłego przechowywania i szybkiej iteracji; std::list rozważyć tylko, gdy naprawdę potrzebujesz stabilnych iteratorów i dużo wstawek/splicingów bez przesuwania elementów.
Dla map:
std::unordered_map dla szybkich, średnich dostępowych ofert (hash table)std::map dla uporządkowanych kluczy i zapytań zakresowychSkup się na kosztach, które się mnożą:
reserve())I zawsze weryfikuj za pomocą profilera zamiast polegać na intuicji.
Ustal reguły i narzędzia, aby wydajność i bezpieczeństwo nie zależały od bohaterstwa pojedynczych programistów:
new/deletereturnJeśli chcesz głębszego przewodnika dotyczącego kontenerów, zobacz: /blog/choosing-cpp-containers.
std::unique_ptrstd::shared_ptrclang-tidyTo pomaga zachować kontrolę C++ przy jednoczesnym ograniczeniu undefined behavior i nieoczekiwanych narzutów.