[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - öröklődés és polimorfizmus

[2022. március 09.] [ christo161 ]

Ebben a tananyagrészben arról lesz szó, hogyan tudunk egy osztály alapján egy hasonló osztályt létrehozni anélkül, hogy újra definiálnunk kelljen az összes adattagot és az összes tagfüggvényt.

előző tananyagrész: több forrásfájlból álló programok
következő tananyagrész: templatek

Tartalom

  • öröklődés
  • polimorfizmus
  • dinamikus kötés, dynamic_cast
  • smart pointerek

Öröklődés

Ebben a példában létrehozunk egy autó osztályt, amiből leszármaztatunk egy taxi osztályt. Ez csak egy szemléltető példa, nyilván több adatot is meg lehetne adni, itt most az autó osztályban csak márka, típus, szín, rendszám adatok szerepelnek, valamint ezt a taxi osztályban kibővítjük a viteldíj adattaggal.

//inheritance example
#include <iostream>
#include <string>
#include <typeinfo>

class car {
public:
  car(std::string p_make, std::string p_model, std::string p_color, std::string p_licence_plate) :
make{p_make}, model{p_model}, color{p_color}, licence_plate{p_licence_plate} {}

  virtual ~car(){}

  friend std::ostream& operator<<(std::ostream& p_ostream, const car& p_car);

protected:
  std::string make;
  std::string model;
  std::string color;
  std::string licence_plate;

  virtual void print(std::ostream& p_ostream) const {
    p_ostream << typeid( *this ).name() << '\n';
    p_ostream << "Marka: " << make << '\n';
    p_ostream << "Tipus: " << model << '\n';
    p_ostream << "Szin: " << color << '\n';
    p_ostream << "Rendszam: " << licence_plate << '\n';
  }
};

std::ostream& operator<<(std::ostream& p_ostream, const car& p_car) {
  p_car.print(p_ostream);
  return p_ostream;
}

class taxi : public car {
public:
  taxi(std::string p_make, std::string p_model, std::string p_color, std::string p_licence_plate, int p_kmprice) :
car(p_make, p_model, p_color, p_licence_plate) { km_price = p_kmprice; }

  virtual ~taxi(){}

  friend std::ostream& operator<<(std::ostream& p_ostream, const taxi& p_taxi);

private:
  int km_price;

  virtual void print(std::ostream& p_ostream) const {
    car::print(p_ostream);
    p_ostream << "Viteldij: " << km_price << " Ft\n";
  }
};

std::ostream& operator<<(std::ostream& p_ostream, const taxi& p_taxi) {
  p_taxi.print(p_ostream);
  return p_ostream;
}

int main() {
  car car1{"Opel", "Astra", "White", "AAA-001"};
  std::cout << car1;

  std::cout.put('\n');

  taxi taxi1{"Ford", "Focus", "Yellow", "BBB-002", 200};
  std::cout << taxi1;
}

Az autó osztályban nem privátként, hanem protectedként definiáljuk az adattagokat, azért, hogy a származtatott taxi osztály is hozzáférhessen azokhoz.

Öröklődés esetén a destruktort virtuális függvényként definiáljuk, ekkor egy származtatott objektum megszűnése esetén először a származtatott osztály destruktora, majd az ősosztály destruktora is lefut. Bár ebben a példában nem tartalmaz utasításokat a destruktor, nem árt megszokni, hogy öröklődés esetén virtuális tagfüggvényként definiáljuk.

Az öröklődés C++ nyelvben a : operátorral valósítható meg, például a következő módon:

class taxi : public car {

Ez azt jelenti, hogy a taxi osztály a car osztály adattagjait és tagfüggvényeit az eredeti access specifierekkel átveszi.
Igény esetén az access specifiereket lehet szűkíteni, pl. ha a public helyett private-ot írnánk, akkor mindent privátként venne át.

A taxi osztály konstruktorában szerepel egy úgynevezett konstruktordelegálás, ami azt jelenti, hogy a car osztály konstruktorát hívjuk meg, és átadjuk neki a megfelelő paramétereket. Ennek köszönhetően a taxi osztály konstruktorában csak a fennmaradó adattagokat kell inicializálni.

taxi(/*...*/) : car(/*...*/) { /*...*/ }

A car osztályban létrehoztunk egy print függvényt, amelynek segítségével megvalósítjuk az adattagok kiíratását. Ez a függvény virtuális, ami azt jelenti, hogy a származtatott osztályban felül lehet definiálni.
A taxi osztály print függvényében először a car osztály print függvényét hívjuk meg, azután pedig kiíratjuk a fennmaradó adattagot, a viteldíjat, így nem kell a taxi osztály print tagfüggvényében is felsorolni az összes adattag kiíratását.

A print tagfüggvényeket nem nyilvánosként definiáltuk, hogy kívülről ne legyen hívható, hiszen a kiíratást az insertion operátorral végezzük el. A print tagfüggvényt a túlterhelt insertion operator blokkjában hívjuk meg.

Polimorfizmus

Szintén egy nagyon leegyszerűsített példa. Itt csak márkája, típusa és hossza van a járműveknek.

Ha egy osztálynak van virtuális tagfüggvénye akkor azt a származtatott osztályokban felüldefiniálhatjuk (bár ilyet csináltunk az előző példában is, ebben a példában talán szemléletesebb).

Ebben az egyszerű példában egy kamionhoz csatolunk egy pótkocsit és amikor lekérdezzük a hosszát a kamionnak, akkor a pótkocsi hossza is hozzá lesz adva (ha van hozzá csatolva pótkocsi). A kamion osztályban felüldefiniált get_length() tagfüggvény valósítja meg a pótkocsi hosszának beszámítását.

//polymorphism example
#include <iostream>
#include <string>

class vehicle {
public:
  vehicle(std::string p_make, std::string p_type, double p_length) :
make{p_make}, type{p_type}, length{p_length} {}

  virtual ~vehicle(){}

  std::string get_name() { return make + " " + type; }

  virtual double get_length() { return length; }

protected:
  std::string make;
  std::string type;
  double length;
};

class bus : public vehicle {
public:
  bus(std::string p_make, std::string p_type, double p_length, int p_passengers = 0) :
vehicle(p_make, p_type, p_length) { passengers = p_passengers; }

  virtual ~bus(){}

private:
  int passengers;
};

class trailer : public vehicle {
public:
  trailer(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~trailer(){}

private:
  //
};

class truck : public vehicle {
public:
  truck(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~truck(){}

  std::string get_trailer_name() {
    if (m_trailer != nullptr) {
      return m_trailer->get_name();
    } else {
      return "";
    }
  }

  virtual double get_length() {
    if (m_trailer != nullptr) {
      return (vehicle::get_length() + m_trailer->get_length());
    } else {
      return vehicle::get_length();
    }
  }

  void set_trailer(trailer& p_trailer) { m_trailer = &p_trailer; }
  void unset_trailer() { m_trailer = nullptr; }

private:
  trailer* m_trailer = nullptr;
};

int main() {
  trailer trailer1{"Krone", "AZW 18", 7.5};
  truck truck1{"DAF","XF 105", 5.96};
  truck1.set_trailer(trailer1);

  std::cout << "Name: " << truck1.get_name() << '\n';
  std::cout << "Trailer name: " << truck1.get_trailer_name() << '\n';
  std::cout << "Total length: " << truck1.get_length() << '\n';
}

Aggregációnak nevezzük, ha egy osztálynak van olyan adattagja, ami túlélheti egy objektum megszűnését, vagyis nem szűnik meg az objektummal együtt. Ebben a példában erre példa egy kamionhoz csatolt trailer.

Dinamikus kötés, dynamic_cast

Ebben a példában ugyanazokat az osztályokat használjuk fel, mint az előző példában.

Létrehozunk egy dinamikusan allokált tömböt, amiben ősosztály típusú pointereket tárolunk, viszont az egyes elemeket származtatott osztály típusúként példányosítjuk.
Ekkor, ha végigiterálunk a tömbön, akkor a származtatott osztálybeli, felüldefiniált tagfüggvények lesznek végrehajtva (annak ellenére, hogy a pointerek ősosztály típusúak). Például a kamion esetén ha van trailer, akkor annak a hosszát bele fogja számolni a kamion hosszába.

Viszont egy ősosztály típusú pointernek nincs get_trailer_name() tagfüggvénye, ezért át kell konvertálni a dynamic_cast operátor segítségével származtatott osztály típusú pointerré ahhoz, hogy ezt a tagfüggvényt rajta keresztül használhassuk.
Az ősosztály típusú pointert természetesen csak akkor akarjuk kamion típusú pointerré átkonvertálni, ha az tényleg egy kamion objektumra mutat. Ezt a következő módon tudjuk ellenőrízni:

if(dynamic_cast<truck*>(vehicles[i])) {

Ez tulajdonképpen egyenértékű a Java nyelvben lévő instanceof operátorral.

//polymorphism example
#include <iostream>
#include <string>

class vehicle {
public:
  vehicle(std::string p_make, std::string p_type, double p_length) :
make{p_make}, type{p_type}, length{p_length} {}

  virtual ~vehicle(){}

  std::string get_name() { return make + " " + type; }

  virtual double get_length() { return length; }

protected:
  std::string make;
  std::string type;
  double length;
};

class bus : public vehicle {
public:
  bus(std::string p_make, std::string p_type, double p_length, int p_passengers = 0) :
vehicle(p_make, p_type, p_length) { passengers = p_passengers; }

  virtual ~bus(){}

private:
  int passengers;
};

class trailer : public vehicle {
public:
  trailer(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~trailer(){}

private:
  //
};

class truck : public vehicle {
public:
  truck(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~truck(){}

  std::string get_trailer_name() {
    if (m_trailer != nullptr) {
      return m_trailer->get_name();
    } else {
      return "";
    }
  }

  virtual double get_length() {
    if (m_trailer != nullptr) {
      return (vehicle::get_length() + m_trailer->get_length());
    } else {
      return vehicle::get_length();
    }
  }

  void set_trailer(trailer& p_trailer) { m_trailer = &p_trailer; }
  void unset_trailer() { m_trailer = nullptr; }

private:
  trailer* m_trailer = nullptr;
};

int main() {
  const int arr_size = 3;
  vehicle* vehicles[arr_size];

  vehicles[0] = new truck{"DAF","XF 105", 5.96};
  trailer trailer1{"Krone", "AZW 18", 7.5};
  dynamic_cast<truck*>(vehicles[0])->set_trailer(trailer1);

  vehicles[1] = new bus{"Credo","EC 11", 10.6};
  vehicles[2] = new bus{"Ikarus","280", 16.5};

  for(int i = 0; i < arr_size; ++i) {
    std::cout << "Name: " << vehicles[i]->get_name() << '\n';
    if(dynamic_cast<truck*>(vehicles[i])) {
      std::cout << "Trailer name: " << dynamic_cast<truck*>(vehicles[i])->get_trailer_name() << '\n';
    }
    std::cout << "Total length: " << vehicles[i]->get_length() << '\n';
    std::cout.put('\n');
  }
}

Smart pointerek

A modern C++ szabvány szerint fordított kódban kerülendő osztálydefiníción kívül new operátort használni, mivel a hozzá tartozó delete operátor nem biztos, hogy végrehajtódik (pl. ha időközben kivétel dobódik). Ezért például smart pointereket érdemes használni hagyományos pointerek (raw pointerek) helyett.

//polymorphism example with smart pointers
#include <iostream>
#include <string>
#include <memory>

class vehicle {
public:
  vehicle(std::string p_make, std::string p_type, double p_length) :
make{p_make}, type{p_type}, length{p_length} {}

  virtual ~vehicle(){}

  std::string get_name() { return make + " " + type; }

  virtual double get_length() { return length; }

protected:
  std::string make;
  std::string type;
  double length;
};

class bus : public vehicle {
public:
  bus(std::string p_make, std::string p_type, double p_length, int p_passengers = 0) :
vehicle(p_make, p_type, p_length) { passengers = p_passengers; }

  virtual ~bus(){}

private:
  int passengers;
};

class trailer : public vehicle {
public:
  trailer(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~trailer(){}

private:
  //
};

class truck : public vehicle {
public:
  truck(std::string p_make, std::string p_type, double p_length) :
vehicle(p_make, p_type, p_length) {}

  virtual ~truck(){}

  std::string get_trailer_name() {
    if (m_trailer != nullptr) {
      return m_trailer->get_name();
    } else {
      return "";
    }
  }

  virtual double get_length() {
    if (m_trailer != nullptr) {
      return (vehicle::get_length() + m_trailer->get_length());
    } else {
      return vehicle::get_length();
    }
  }

  void set_trailer(trailer& p_trailer) { m_trailer = std::make_unique<trailer>(p_trailer); }
  void unset_trailer() { m_trailer.release(); }

private:
  std::unique_ptr<trailer> m_trailer;
};

int main() {
  const int arr_size = 3;
  std::shared_ptr<vehicle> vehicles[arr_size];

  vehicles[0] = std::make_shared<truck>("DAF","XF 105", 5.96);
  trailer trailer1{"Krone", "AZW 18", 7.5};
  std::dynamic_pointer_cast<truck>(vehicles[0])->set_trailer(trailer1);

  vehicles[1] = std::make_shared<bus>("Credo","EC 11", 10.6);
  vehicles[2] = std::make_shared<bus>("Ikarus","280", 16.5);

  for(int i = 0; i < arr_size; ++i) {
    std::cout << "Name: " << vehicles[i]->get_name() << '\n';
    if(std::dynamic_pointer_cast<truck>(vehicles[i])) {
      std::cout << "Trailer name: " << std::dynamic_pointer_cast<truck>(vehicles[i])->get_trailer_name() << '\n';
    }
    std::cout << "Total length: " << vehicles[i]->get_length() << '\n';
    std::cout.put('\n');
  }
}

Az std::unique_ptr-t nem lehet dynamic_castolni, ezért dynamic_castolás esetén std::shared_ptr-t használunk.

Egyéb tananyag

előző tananyagrész: több forrásfájlból álló programok
következő tananyagrész: templatek

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr8417374316

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása