programowanieProgramista C++

Czym są const-expressions (constexpr) w C++? W jakich przypadkach i dlaczego je stosować, oraz czym różnią się od makr i const?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Historia pytania

C++ początkowo wspierał tylko makra (#define) i stałe (const). Jednak do celów określenia wartości w czasie kompilacji było to niewystarczające. W C++11 wprowadzono słowo kluczowe constexpr, które pozwala na obliczanie wartości już na etapie kompilacji, a nie tylko w czasie wykonywania programu.

Problem

Przed pojawieniem się constexpr wiele zadań należało rozwiązywać za pomocą makr (prymitywnego zastępnika tekstowego bez bezpieczeństwa typów) lub za pomocą const, który nie zawsze gwarantował wykonanie wyrażenia na etapie kompilacji. To utrudniało optymalizację programu i prowadziło do mniej przewidywalnego zachowania.

Rozwiązanie

constexpr gwarantuje kompilatorowi, że zadeklarowane wyrażenie będzie obliczone w czasie kompilacji, jeśli to możliwe. Używa się go do deklaracji funkcji, zmiennych, a nawet konstruktorów i metod klas, które bezpiecznie i efektywnie są obliczane podczas kompilacji.

Przykład kodu:

constexpr int Square(int x) { return x * x; } constexpr int size = Square(5); // size obliczane na etapie kompilacji const int arr[size] = {}; // można użyć jako rozmiaru tablicy

Kluczowe cechy:

  • Gwarantuje obliczenie na etapie kompilacji (jeśli to możliwe).
  • Pozwala deklarować nie tylko zmienne, ale także funkcje, metody, konstruktorów.
  • Poprawia wydajność dzięki wcześniejszym obliczeniom i bezpieczeństwu typów.

Pytania z podstępem.

Czy można używać dowolnej funkcji jako constexpr?

Nie. Funkcja musi spełniać szereg ograniczeń: być wystarczająco prosta, zawierać jedną linię return (do C++14) lub tylko kod do obliczeń stałych (od C++14 wzwyż).

constexpr int f(int x) { return x + 2; } // ok constexpr int g(int x) { int y = x + 2; return y; } // do C++14: nie kompiluje się! po — można

Czy wszystkie zmienne constexpr mogą być obliczane w czasie kompilacji?

Nie. Jeśli podczas inicjalizacji używane jest nie-stałe wartości lub wyrażenie nie udaje się obliczyć w czasie kompilacji, wystąpi błąd.

int val; // constexpr int x = f(val); // Błąd: val nie został zainicjalizowany!

Jaka jest różnica między constexpr a const?

const gwarantuje tylko niemożność zmiany, ale nie gwarantuje obliczenia w czasie kompilacji. constexpr wymaga obliczenia wartości w czasie kompilacji (jeśli to możliwe).

const int x = time(nullptr); // ok, ale obliczane w czasie wykonywania constexpr int y = 42; // ok, obliczane w czasie kompilacji

Typowe błędy i antywzorce

  • Mylenie const i constexpr
  • Próba użycia złożonych konstrukcji logicznych w funkcjach constexpr przed C++14
  • Nieprawidłowe użycie zmiennych nie-stałych w kontekście constexpr

Przykład z życia

Negatywny przypadek

Programista używa #define PI 3.14 do wszystkich obliczeń powierzchni koła.

Zalety:

  • Łatwo napisać

Wady:

  • Brak bezpieczeństwa typów, możliwy błąd podstawienia
  • Nie można używać jako constexpr w szablonach lub parametrach tablicy

Pozytywny przypadek

Programista używa constexpr double PI = 3.141592653589793; oraz szablonowych funkcji constexpr do obliczeń.

Zalety:

  • Bezpieczeństwo typów
  • Optymalizacja podczas kompilacji
  • Wszechstronność użycia (na przykład w szablonach).

Wady:

  • Trochę wyższe wymagania co do zrozumienia kodu; potrzebne wsparcie C++11+