Historia pytania: Termin "syntaktyczny cukier" zaproponował Peter Landin w latach 60. XX wieku. W C++ od samego początku wbudowane są konstrukcje-wrapped, które ułatwiają pisanie i odbiór kodu, nie dodając nowych możliwości w porównaniu do tego, co można wyrazić bardziej szczegółową podstawową składnią.
Problem: Głównym celem syntaktycznego cukru jest uczynienie kodu bardziej zwięzłym, ekspresyjnym i czytelnym, zmniejszenie prawdopodobieństwa błędów i przyspieszenie rozwoju. Z drugiej strony nadmierne komplikowanie składni prowadzi do zamieszania i ukrytych problemów z wydajnością lub zrozumieniem kodu.
Rozwiązanie: W C++ do syntaktycznego cukru zalicza się wiele konstrukcji, które w zasadzie są owijkami nad bardziej podstawowymi elementami języka. Przykłady: przeciążenia operatorów, pętla for oparta na zakresie, listy inicjalizacyjne, auto, wyrażenia lambda.
Przykład kodu:
std::vector<int> v = {1, 2, 3, 4}; for (auto x : v) { std::cout << x << std::endl; } // Równoważne (bez cukru): for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) { std::cout << *it << std::endl; }
Kluczowe cechy:
Czy auto zastępuje typowanie na etapie kompilacji i czy jest dodatkowe obciążenie związane z jego użyciem?
Nie, auto jest całkowicie wnioskowane na etapie kompilacji, bez strat na wydajności, jeśli jest używane prawidłowo. Błędy występują tylko wtedy, gdy przez przypadek auto nie jest tym typem, którego się spodziewano.
Czy for (auto x : v) jest zawsze najszybszym sposobem iterowania po kontenerze?
Nie. Taki składnia może kopiować elementy (jeśli nie użyto &), co prowadzi do straty wydajności przy dużych obiektach. Aby uniknąć tego, zaleca się użycie referencji:
for (auto& x : v) { ... }
Czy przeciążenie operatorów zawsze czyni kod bardziej zrozumiałym?
Nie! Przeciążenie operatorów można wykorzystać również na szkodę — jeśli operatory są przeciążane niesemantycznie (np. przeciążenie operatora + tak, aby usuwał elementy), kod staje się bardziej zawiły.
Użycie auto bez uwzględnienia typu zwracającego funkcją-iteratorem:
std::vector<std::pair<int, int>> data; for (auto x : data) { x.first = 0; } // Modyfikacja nie nastąpi, ponieważ to kopia
Zalety:
Wady:
for (auto& x : data) { x.first = 0; } // Teraz modyfikacja jest efektywna
Zalety:
Wady: