Programozás alapjai II. (2. ea) C++

Névterek, memóriakezelés

Szeberényi Imre, Somogyi Péter BME IIT szebi@iit.bme.hu

Hol tartunk?

Névterek, scope operátor

namespace nevterem {
  int alma;
  float fv(int i);
  char *nev;
} // namespace nevterem
void a() {
  nevterem::alma = 12;
  float f = nevterem::fv(5);
}
using namespace nevterem;
void b() {
  alma = 8;
  float f = fv(3);
}

using direktíva

Név nélküli névtér

Biztosítani akarjuk, hogy egy kódrészlet csak az adott fordítási egységből legyen elérhető. Névütközés biztosan nem lesz.

namespace { // nincs neve
  void solveTheProblem() { /* ... */ }
  // ...
} // névtér vége
int main() {
  solveTheProblem();
  // ...
}

Névterek egymásba ágyazása, alias

Argument-dependent lookup (ADL)

Nem minősített függvények hívásakor a fv. argumentumainak névterében is keres.

namespace N {
  struct P { int x, y; };
  void fx(P p) { /* ... */ }
  void fy(int i) { /* ...*/ }
  P origo;
} // namespace N

int main() {
  fx(N::origo); // N-ben keres
  N::fy(4);
}

Az std névtér

Standard I/O madártávlatból

#include <iostream>
using std::cout;
using std::endl;

int main() {
  cout << "Hello C++\n";
  //->   std::operator<<(std::cout, "Hello C++\n");
  //       fv, overload + ADL miatt ^
  int i;
  std::cin >> i;
  cout << "2 * " << i << " = " << 2 * i << endl;
}

<iostream>

Standard I/O objektumait definiáló fejléc fájl valahogy így néz ki:

#include <ios>
#include <streambuf> 
#include <istream> 
#include <ostream>
namespace std {
    extern istream cin; 
    extern ostream cout; 
    extern ostream cerr; 
    extern ostream clog; 
// ...

Manipulátorok

#include <iomanip>

Hatás szempontjából 3 fajtájuk van:

https://infocpp.iit.bme.hu/iomanip

Inline függvények (ism.)

inline int max(int a, int b) { return (a < b ? b : a); }
inline long max(long a, long b) { return (a < b ? b : a); }
inline double max(double a, double b) { return (a < b ? b : a); }

Sablon

Nyelvi elem

template <typename T> // korábban class volt a typename
//                 ^  formális sablonparaméter
inline T max(T a, T b) {
  return a < b ? b : a;
} // hatókör: a template kulcsszót követő dekl./def. vége

#include <iostream>
using namespace std;
int main() {
  cout << ::max<long>(2, 10);
  //             ^ aktuális sablonparaméter
  cout << ::max<double>(2.5, 3.14);
  cout << ::max(40, 50);
  //            ^   ^ levezethető a paraméterekből
}

Mi a sablon (template)?

Minden típusra jó ez a max?

template <typename T> T max(T a, T b) {
// inline felesleges, mert...
  return a < b ? b : a;
}
#include <iostream>
using namespace std;
int main() {
  cout << ::max(40, 50);
  cout << ::max("alma", "korte");
  // "alma" -> const char *
}

1. megoldás

Összehasonlító fv.:

template <typename T> T max(T a, T b) { return a < b ? b : a; }
// max 3 paraméteres változata (overload)
template <typename T, typename C> T max(T a, T b, C cmp) {
  return cmp(a, b) ? b : a; //       predikátum   ^
}
#include <cstring>
bool strLess(const char *s1, const char *s2) {
  return strcmp(s1, s2) < 0;
}
#include <iostream>
int main() {
  std::cout << max(40, 50);                   // 50
  std::cout << max("alma", "korte", strLess); // korte
}

2. megoldás

Template specializáció:

template <typename T> T max(T a, T b) { return a < b ? b : a; }
// Teljes specializáció T::= const char* esetre
#include <cstring>
template <>
inline const char *max(const char *a, const char *b) {
  return strcmp(a, b) > 0 ? a : b;
}
#include <iostream>
int main() {
  std::cout << max<long>(2, 10) << std::endl;     // 10
  std::cout << max<double>(1, 3.14) << std::endl; // 3.14
  std::cout << max(40, 50) << std::endl;          // 50
  std::cout << max("alma", "korte") << std::endl; // korte
  std::cout << max("Ádám", "Béla") << std::endl;  // Ádám ??
}

Ékezettel C-ben is baj volt

Egy betű → egy karakter kódolásnál strcoll() fv.

#include <cstring>
template <typename T> T max(T a, T b) { return a < b ? b : a; }
template <> const char *max(const char *a, const char *b) {
  return strcoll(a, b) < 0 ? b : a;
}
#include <iostream>
int main() {
  setlocale(LC_ALL, "");
  std::cout << max("Ádám", "Béla") << std::endl; // Béla
}

Függvénysablon összefoglalás

Predikátum

template <typename T, typename P>
T legElem(T a[], int n, P pred) {
  T tmp = a[0];
  for (int i = 1; i < n; ++i) {
    if (pred(a[i], tmp)) {
      tmp = a[i];
    }
  }
  return tmp;
}

Predikátum példa

// template <typename T, typename P>
// T legElem(T a[], int n, P pred) { /* ... */ }

// a predikátum is lehet template
template <typename T> bool nagyobb_e(T a, T b) {
  return a > b;
}
#include <iostream>
int main() {
  int tomb[] = {1, 3, 4, 80, -21};
  std::cout << legElem(tomb, 5, nagyobb_e<int>);
}

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_02/ → predicate

Dinamikus memória

C:

#include <malloc.h>
struct Lanc *p;
p = malloc(sizeof(Lanc));
if (p == NULL) {
 ; // ...
}
free(p);

CPP:

Lanc *l;
l = new Lanc;
// ...
delete l;
// Tömb:
int *t;
t = new int[10];
delete[] t;

Példa: fordit_main.cpp

Számokat olvasunk be és fordított sorrendben kiírjuk. Láncban tárolunk.

#include <iostream>
#include "szimpla_lanc.hpp"
using std::cout;
using std::cin;
int main() {
    int i;
    Lanc_elem* kezdo = NULL;  // üres lánc
    while ((cin >> i) != NULL) { kezdo = lanc_epit(kezdo, i); }
    cout << "Adatok fordított sorrendben:" << std::endl;
    lanc_kiir(cout, kezdo);
    lanc_felszabadit(kezdo);
}
#ifndef SZIMPLA_LANC_HPP
#define SZIMPLA_LANC_HPP
#include <iostream>
struct Lanc_elem { // Struktúra név típussá vált
  int adat;
  Lanc_elem *kov;
};
Lanc_elem *lanc_epit(Lanc_elem *p, int i);
void lanc_kiir(std::ostream &os, const Lanc_elem *p);
//               ^ Névteret nem nyitunk .h-ban!
void lanc_felszabadit(Lanc_elem *p);
#endif // SZIMPLA_LANC_HPP

Példa: szimpla_lanc

#include "szimpla_lanc.hpp"

Lanc_elem *lanc_epit(Lanc_elem *p, int a) {
  Lanc_elem *uj = new Lanc_elem;
  //               ^ Vigyázat ez operátor!
  // Nem szabad a malloc-ot formálisan lecserélni
  // mert mást jelent!
  uj->adat = a;
  uj->kov = p;
  return uj;
}
void lanc_kiir(std::ostream &os, const Lanc_elem *p) {
  while (p != NULL) {
    os << p->adat << ' ';
    p = p->kov;
  }
}

void lanc_felszabadit(Lanc_elem *p) {
  while (p != NULL) {
    Lanc_elem *tmp = p->kov;
    delete p;
    p = tmp;
  }
}
void lanc_kiir(std::ostream &os, const Lanc_elem *p) {
  while (p != NULL) {
    os << p->adat << ' ';
    p = p->kov;
  }
}

void lanc_felszabadit(Lanc_elem *p) {
  while (p != NULL) {
    Lanc_elem *tmp = p->kov;
    delete p;
    p = tmp;
  }
}

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_02/ → fordít

Mi van, ha elfogy a memória?

new_handler

#include <cstdlib>
#include <iostream>
void outOfMem() {
  std::cerr << "Gáz van\n";
  // Ha visszatérne, akkor a new ciklusban hívná. 
  exit(1);
}

int main() {
  std::set_new_handler(outOfMem);
  double *p = new double;
  delete p;
}

Kivételes esetek

Példák kivételes esetre

Kivételes esetek kezelése, jelzése

Kivételkezelés

Kivételkezelés = globális goto

#include <stdexcept>
// ...
valami;
try {
  // ... Kritikus művelet1 pl. egy függvény hívása, aminek a 
  // belsejében:
  if (hiba) throw kifejezes_tipus1("Légy");
  // ... Kritikus művelet2 pl. egy másik függvény hívása,
  // aminek a belsejében:
  if (hiba) throw kifejezes_tipus2("leves");  
  } catch (tipus1& param) {
  // ... Kivételkezelés1
  } catch (tipus2& param) {
  // ... Kivételkezelés2
  }
#include <stdexcept>
double osztas(int y) {
  if (y == 0) { // Hiba/kivétel észlelése
    throw "Osztás nullával";
    // ^ A kivételt azonosító érték eldobása.
  }
  return (5.0 / y);
}
#include <iostream>
using namespace std;
int main() {
  try { // Felügyelt szakasz. Ennek a működése
    // során fordulhat elő a kivételes eset.
    cout << "5/2 =" << osztas(2) << endl;
    cout << "5/0 =" << osztas(0) << endl;
  // Kivétel elkapása és kezelése:
  } catch (const char *p) { // Típus azonosít (köt össze). 
    cout << p << endl;
  }
}

Kivételkezelés a memóriára

#include <iostream>
using namespace std;
int main() {
  long db = 0;
  try { // A kivételes eset itt keletkezhet
    while (true) {
      double *p = new double[1022];
      db++; //     ^  kivételes eset lehet
    }
    // Kivétel elkapása és kezelése:
  } catch (std::bad_alloc &) {
    cerr << "Gaz van" << std::endl;
  } // TODO: delete[] p !!!
  cerr << "Ennyi new sikerult:" << db << endl;
}

Futási példa az ural2-n

ural2:~$ cp ~szebi/proga2/mem_alloc.cpp .
ural2:~$ g++ -static mem_alloc.cpp  -o mem_alloc
ural2:~$ ( ulimit -d 24; ./mem_alloc )

A -static azért kell, hogy a betöltésnél ne legyen szükség extra loader-re, ami extra memóriát használ. A zárójel azért kell, hogy új shell induljon.

Az ural2-n levő kóddal a new_handler működése is tesztelhető. Ehhez a fordításkor definiálni kell a HANDLER azonosítót:

ural2:~$ g++ -static mem_alloc.cpp –DHANDLER  -o mem_alloc
ural2:~$ ( ulimit -d 24; ./mem_alloc )