Witamy z powrotem w języku C++ — Modern C++
Od czasu jego utworzenia język C++ stał się jednym z najczęściej używanych języków programowania na świecie. Dobrze napisane programy języka C++ są szybkie i wydajne. Język jest bardziej elastyczny niż inne języki: może działać na najwyższym poziomie abstrakcji i w dół na poziomie krzemu. Język C++ dostarcza wysoce zoptymalizowane biblioteki standardowe. Umożliwia ona dostęp do funkcji sprzętowych niskiego poziomu, aby zmaksymalizować szybkość i zminimalizować wymagania dotyczące pamięci. Język C++ może tworzyć niemal dowolny rodzaj programu: gry, sterowniki urządzeń, HPC, chmura, komputery stacjonarne, osadzone i mobilne oraz wiele innych. Nawet biblioteki i kompilatory dla innych języków programowania są zapisywane w języku C++.
Jednym z oryginalnych wymagań dla języka C++ było zgodność z poprzednimi wersjami języka C. W związku z tym język C++ zawsze zezwalał na programowanie w stylu C z nieprzetworzonymi wskaźnikami, tablicami, ciągami znaków zakończonymi wartościami null i innymi funkcjami. Mogą one zapewnić doskonałą wydajność, ale mogą również powodować błędy i złożoność. Ewolucja języka C++ podkreśliła funkcje, które znacznie zmniejszają potrzebę używania idiomów w stylu C. Stare obiekty programowania C są nadal tam, gdy ich potrzebujesz. Jednak w nowoczesnym kodzie C++ należy ich potrzebować mniej i mniej. Nowoczesny kod C++ jest prostszy, bezpieczniejszy, bardziej elegancki i wciąż tak szybki, jak zawsze.
Poniższe sekcje zawierają omówienie głównych funkcji nowoczesnego języka C++. O ile nie określono inaczej, wymienione tutaj funkcje są dostępne w języku C++11 lub nowszym. W kompilatorze Microsoft C++ można ustawić /std
opcję kompilatora, aby określić, która wersja standardu ma być używana dla projektu.
Zasoby i inteligentne wskaźniki
Jedną z głównych klas błędów w programowaniu w stylu C jest wyciek pamięci. Przecieki są często spowodowane przez błąd wywołania delete
pamięci przydzielonej za pomocą new
polecenia . Modern C++ podkreśla zasadę pozyskiwania zasobów jest inicjowanie (RAII). Pomysł jest prosty. Zasoby (pamięć sterty, dojścia plików, gniazda itd.) powinny być własnością obiektu. Ten obiekt tworzy lub odbiera nowo przydzielony zasób w konstruktorze i usuwa go w destruktorze. Zasada RAII gwarantuje, że wszystkie zasoby są prawidłowo zwracane do systemu operacyjnego, gdy obiekt będąc właścicielem wykracza poza zakres.
Aby ułatwić wdrażanie zasad RAII, biblioteka Standardowa języka C++ udostępnia trzy inteligentne typy wskaźników: std::unique_ptr
, std::shared_ptr
i std::weak_ptr
. Inteligentny wskaźnik obsługuje alokację i usunięcie pamięci, która jest jej właścicielem. W poniższym przykładzie pokazano klasę z składową tablicy przydzieloną na stercie w wywołaniu metody make_unique()
. Wywołania klasy new
i delete
są hermetyzowane przez klasę unique_ptr
. widget
Gdy obiekt wykracza poza zakres, zostanie wywołany destruktor unique_ptr i zwolni pamięć przydzieloną dla tablicy.
#include <memory>
class widget
{
private:
std::unique_ptr<int[]> data;
public:
widget(const int size) { data = std::make_unique<int[]>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime automatically tied to enclosing scope
// constructs w, including the w.data gadget member
// ...
w.do_something();
// ...
} // automatic destruction and deallocation for w and w.data
Jeśli to możliwe, użyj inteligentnego wskaźnika do zarządzania pamięcią stert. Jeśli musisz jawnie użyć new
operatorów i delete
, postępuj zgodnie z zasadą RAII. Aby uzyskać więcej informacji, zobacz Okres istnienia obiektów i zarządzanie zasobami (RAII).
std::string
i std::string_view
Ciągi w stylu C są kolejnym głównym źródłem usterek. Za pomocą poleceń std::string
i std::wstring
można wyeliminować praktycznie wszystkie błędy skojarzone z ciągami w stylu C. Zyskujesz również korzyść z funkcji składowych na potrzeby wyszukiwania, dołączania, dołączania, dołączania i tak dalej. Oba są wysoce zoptymalizowane pod kątem szybkości. Podczas przekazywania ciągu do funkcji, która wymaga tylko dostępu tylko do odczytu, w języku C++17 można użyć std::string_view
w celu uzyskania jeszcze większej korzyści wydajności.
std::vector
i inne kontenery biblioteki standardowej
Standardowe kontenery biblioteki są zgodne z zasadą RAII. Zapewniają iteratory do bezpiecznego przechodzenia elementów. Są one wysoce zoptymalizowane pod kątem wydajności i zostały dokładnie przetestowane pod kątem poprawności. Korzystając z tych kontenerów, można wyeliminować potencjał błędów lub nieefektywności, które mogą zostać wprowadzone w niestandardowych strukturach danych. Zamiast nieprzetworzonych tablic użyj vector
jako kontenera sekwencyjnego w języku C++.
vector<string> apples;
apples.push_back("Granny Smith");
Użyj map
wartości (nie unordered_map
) jako domyślnego kontenera asocjacji. Użyj set
wartości , multimap
i multiset
w przypadku degeneracji i wielu przypadków.
map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";
W razie potrzeby optymalizacji wydajności rozważ użycie:
- Nieurządkowane kontenery asocjacyjne, takie jak
unordered_map
. Mają one mniejsze obciążenie poszczególnych elementów i wyszukiwanie w stałym czasie, ale mogą być trudniejsze do prawidłowego i wydajnego użycia. - Posortowane
vector
. Aby uzyskać więcej informacji, zobacz Algorytmy.
Nie używaj tablic w stylu C. W przypadku starszych interfejsów API, które wymagają bezpośredniego dostępu do danych, należy użyć metod dostępu, takich jak f(vec.data(), vec.size());
. Aby uzyskać więcej informacji na temat kontenerów, zobacz C++ Standard Library Containers (Kontenery biblioteki standardowej języka C++).
Algorytmy biblioteki standardowej
Przed założeniem, że musisz napisać niestandardowy algorytm dla programu, najpierw zapoznaj się z algorytmami standardowej biblioteki języka C++. Biblioteka Standardowa zawiera coraz większy asortyment algorytmów dla wielu typowych operacji, takich jak wyszukiwanie, sortowanie, filtrowanie i losowanie. Biblioteka matematyczna jest obszerna. W języku C++17 lub nowszym udostępniane są równoległe wersje wielu algorytmów.
Oto kilka ważnych przykładów:
for_each
, domyślny algorytm przechodzenia (wraz z pętlami opartymi nafor
zakresie).transform
, dla nie-w miejscu modyfikacji elementów kontenerafind_if
, domyślny algorytm wyszukiwania.sort
,lower_bound
i inne domyślne algorytmy sortowania i wyszukiwania.
Aby napisać komparator, należy używać surowych <
i używać nazwanych lambdów , gdy to możliwe.
auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );
auto
zamiast jawnych nazw typów
Język C++11 wprowadził auto
słowo kluczowe do użycia w deklaracjach zmiennych, funkcji i szablonów. auto
polecenie kompilatorowi, aby wyłudić typ obiektu, aby nie trzeba było wpisać go jawnie. auto
jest szczególnie przydatne, gdy typ wywołany jest szablonem zagnieżdżonym:
map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++
Pętle oparte na for
zakresie
Iteracja w stylu C dla tablic i kontenerów jest podatna na błędy indeksowania i jest również żmudna do wpisywania. Aby wyeliminować te błędy i zwiększyć czytelny kod, użyj pętli opartych na for
zakresie zarówno z kontenerami biblioteki standardowej, jak i nieprzetworzonymi tablicami. Aby uzyskać więcej informacji, zobacz Instrukcja oparta na for
zakresie.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v {1,2,3};
// C-style
for(int i = 0; i < v.size(); ++i)
{
std::cout << v[i];
}
// Modern C++:
for(auto& num : v)
{
std::cout << num;
}
}
constexpr
wyrażenia zamiast makr
Makra w języku C i C++ to tokeny przetwarzane przez preprocesor przed kompilacją. Każde wystąpienie tokenu makra jest zastępowane zdefiniowaną wartością lub wyrażeniem przed skompilowanym plikiem. Makra są często używane w programowaniu w stylu C do definiowania wartości stałych w czasie kompilacji. Jednak makra są podatne na błędy i trudne do debugowania. W nowoczesnym języku C++należy preferować constexpr
zmienne dla stałych czasu kompilacji:
#define SIZE 10 // C-style
constexpr int size = 10; // modern C++
Jednolite inicjowanie
W nowoczesnym języku C++można użyć inicjowania nawiasu klamrowego dla dowolnego typu. Ta forma inicjowania jest szczególnie wygodna podczas inicjowania tablic, wektorów lub innych kontenerów. W poniższym przykładzie v2
zainicjowano trzy wystąpienia klasy S
. v3
jest inicjowany z trzema S
wystąpieniami, które same są inicjowane przy użyciu nawiasów klamrowych. Kompilator wywnioskuje typ każdego elementu na podstawie zadeklarowanego typu v3
.
#include <vector>
struct S
{
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
int main()
{
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7);
S s2("Frank", 3.5);
S s3("Jeri", 85.9);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
// Modern C++:
std::vector<S> v2 {s1, s2, s3};
// or...
std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };
}
Aby uzyskać więcej informacji, zobacz Inicjowanie nawiasu klamrowego.
Przenoszenie semantyki
Nowoczesny język C++ zapewnia semantyka przenoszenia, co umożliwia wyeliminowanie niepotrzebnych kopii pamięci. We wcześniejszych wersjach języka kopie były nieuniknione w niektórych sytuacjach. Operacja przenoszenia przenosi własność zasobu z jednego obiektu do następnego bez tworzenia kopii. Niektóre klasy posiadają zasoby, takie jak pamięć sterta, dojścia plików itd. Implementując klasę będącą właścicielem zasobów, można zdefiniować dla niej konstruktor przenoszenia i operator przypisania przenoszenia. Kompilator wybiera te specjalne elementy członkowskie podczas rozwiązywania przeciążeń w sytuacjach, gdy kopia nie jest potrzebna. Typy kontenerów biblioteki standardowej wywołują konstruktor przenoszenia dla obiektów, jeśli jest zdefiniowany. Aby uzyskać więcej informacji, zobacz Przenoszenie konstruktorów i operatorów przypisania przenoszenia (C++).
Wyrażenia lambda
W programowaniu w stylu C funkcja może być przekazywana do innej funkcji przy użyciu wskaźnika funkcji. Wskaźniki funkcji są niewygodne, aby zachować i zrozumieć. Funkcja, do której się odwołuje, może być zdefiniowana gdzie indziej w kodzie źródłowym, daleko od punktu, w którym jest wywoływana. Ponadto nie są one bezpieczne. Nowoczesny język C++ udostępnia obiekty funkcji, klasy, które zastępują operator()
operator, co umożliwia ich wywoływanie jak funkcja. Najwygodniejszym sposobem tworzenia obiektów funkcji jest użycie wbudowanych wyrażeń lambda. W poniższym przykładzie pokazano, jak za pomocą wyrażenia lambda przekazać obiekt funkcji, który find_if
będzie wywoływany przez funkcję dla każdego elementu w wektorze:
std::vector<int> v {1,2,3,4,5};
int x = 2;
int y = 4;
auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });
Wyrażenie [=](int i) { return i > x && i < y; }
lambda może być odczytywane jako "funkcja, która przyjmuje pojedynczy argument typu int
i zwraca wartość logiczną wskazującą, czy argument jest większy niż x
i mniejszy niż y
." Zwróć uwagę, że zmienne x
i y
z otaczającego kontekstu mogą być używane w lambda. Określa [=]
, że te zmienne są przechwytywane przez wartość; innymi słowy wyrażenie lambda ma własne kopie tych wartości.
Wyjątki
Nowoczesny język C++ podkreśla wyjątki, a nie kody błędów, jako najlepszy sposób raportowania i obsługi warunków błędów. Aby uzyskać więcej informacji, zobacz Modern C++ best practices for exceptions and error handling (Nowoczesne rozwiązania w języku C++ dotyczące wyjątków i obsługi błędów).
std::atomic
Użyj struktury standardowej biblioteki std::atomic
języka C++ i powiązanych typów dla mechanizmów komunikacji międzywątkowa.
std::variant
(C++17)
Związki są często używane w programowaniu w stylu C do oszczędzania pamięci przez umożliwienie członkom różnych typów zajmowania tej samej lokalizacji pamięci. Jednak związki nie są bezpieczne i podatne na błędy programowania. Język C++17 wprowadza klasę std::variant
jako bardziej niezawodną i bezpieczną alternatywę dla związków zawodowych. Funkcja std::visit
może służyć do uzyskiwania dostępu do składowych variant
typu w bezpieczny sposób.
Zobacz też
Dokumentacja języka C++
Wyrażenia lambda
Standardowa biblioteka C++
Zgodność języka Microsoft C/C++
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla