ITHub

Mélyvíz: A C++ és a zárójelek

Mélyvíz: A C++ és a zárójelek
Szabolcsi Judit
Szabolcsi Judit
| ~4 perc olvasás

A C++ egy régi nyelv, az 1980-as években született meg. 1998-ban sikerült szabványosítani, ezért ez a változat a C++98 nevet viseli, ez a klasszikus C++. Azóta több új szabványt is bevezettek: a C++11, C++14 és a C++17-et, és tervezetként a C++20 is elkészült. A C++11-től kezdve beszélhetünk a modern C++-ról. Mivel a C++ egy olyan viszonylag régi nyelv, amit folyamatosan modernizálnak és közben igyekeznek a visszamenőleges kompatibilitást megőrizni, ez rányomja a bélyegét: elég bonyolult és többféle szintaktika él egymás mellett.

Nézzük meg ezt egy látszólag egyszerű témán keresztül: kezdőértékadás változóknak (inicializálás). Először vegyünk egy szimpla int változót:

int x(0); // az x legyen nulla
int y = 0; // az y legyen nulla
int z{ 0 }; // a z is legyen nulla
int v = { 0 }; // a v is legyen nulla

Mind a négy változat helyes, lefordul és ugyanazt csinálja. Kicsit túl sok lehetőségünk van ugyanarra, nem? Ezért a C++11-től kezdve bevezették az egységes inicializálást (uniform initialization), amely szerint használjuk mindig a { }-t kezdőértékadáskor. Ez a forma használható osztályokon belül is:

class Widget {
  private: int x{ 0 }; // az x nem-statikus adattag legyen nulla
};

Nem másolható objektumoknál is:

std::atomic<int> ai{ 0 };

További előnyei is vannak:

Nem engedi meg a szűkítő konverziót, mert az adatvesztéshez vezethet:

double x, y, z
//x,y,z-be kerül valami érték
int sum1{ x + y + z }; //hiba: double -> int konverzió lenne

Érdekességként: a fenti példában a ( )-es és az =-t használó kezdőértékadás megengedi, hogy x+y+z belerakjuk a sum1-ba.

Elkerülünk vele egy bosszantó hibát (most vexing parse):

Widget w1(10); //konstruktorhívás a 10 kezdőértékkel
Widget w2(); //NEM konstruktorhívás!

C++-ban a második sor nem paraméter nélküli konstruktorhívást jelent, hanem egy Widget visszatérési értékű, w2 nevű függvény deklarációját. Ha viszont ezt a formát használjuk:

Widget w{ 10 };
Widget w3{};

Itt már a második is konstruktorhívás.

Tehát úgy tűnik, a {}-es forma tökéletes, használjuk mindig ezt és kész! Sajnos azért nem ilyen egyszerű az élet… Nézzük meg ezt a két sort, ahol std::vector generikus tárolót használunk, ami egy dinamikusan nyújtózkodó tömb:

std::vector<int> v1(10, 20);
std::vector<int> v2{ 10, 20 };

Itt is van () és {} forma. Vajon itt is ugyanazt jelentik? Hát nem... A v1 vectornál a kétparaméteres konstruktort hívjuk meg és ez létrehoz egy 10 elemű vectort, amiben 10 darab 20-as szám lesz. A v2 vectornál viszont az inicializációs listás konstruktor hívódik meg (initializer_list) és kételemű vector jön létre, amiben a 10 és a 20 lesz eltárolva.

Az inicializációs listás konstruktor nagyon „erőszakos”, ha van ilyen az osztályban, akkor elnyomja még a jobban illeszkedő többi konstruktort is, amennyiben az objektum után {} áll. Ez történik a következő példában is:

class Widget {
  public: Widget(int i, bool b); // 1.
  Widget(int i, double d); // 2.
  Widget(std::initializer_list<long double> il); // 3.
};

Widget w1(10, true); // az első konstruktort hívja meg
Widget w2{ 10, true }; // a 3.-at hívja meg és a 10-ből és a true-ból long double-t csinál
Widget w3(10, 5.0); // A 2. hívja meg
Widget w4{ 10, 5.0 }; // ez is a 3.-at hívja meg

Ez a jelenség főleg a sablonosztályok íróit állítja lehetetlen döntés elé. Akár ()-lel, akár {}-lel hozzák létre a sablonban a szükséges objektumokat, a sablon használatakor mindkettő okozhat gondot a felhasználónak. A C++ programozók egy része a {}-re szavaz, a fentebb leírt előnyök miatt, de tudja, hogy pl. a vector használatánál jobb a () vagy a std::vector v2 = { 10, 20 }; forma. A programozók másik része a ()-et részesíti előnyben, mert így továbbviheti a klasszikus C++ szintaktikát, nem fog bezavarni az inicializációs listás konstruktor és persze amikor elkerülhetetlen, akkor használja a {}-t: std::vector<int> v2{ 10, 20,30,40 }; vagy std::vector<int> v2 = { 10, 20, 30, 40 };

Kicsit olyan ez, mit a szóköz kontra tabulátor vita...

Forrás: Scott Meyers: Effective Modern C++

A szerzőről: https://progsuli.hu/ki-vagyok/