Dziedziczenie i polimorfizm

enum

W C++ istnieje możliwość zdefiniowania nowego typu o określonej liczbie możliwych wartości, a ponadto każda z tych wartości jest reprezentowana przez czytelną nazwę. Ta konstrukcja to typy wyliczeniowe. Definicja nowego typu wyliczeniowego:

enum class NewEnumerableType {
  FIRST_VALUE,
  SECOND_VALUE,
  THIRD_VALUE
};

Funkcja teraz może przyjmować typu jako argument:

std::string NewEnumerableTypeToString(NewEnumerableType type) {
  switch(type) {
  case NewEnumerableType::FIRST_VALUE:
    return "FIRST_VALUE";
  case NewEnumerableType::SECOND_VALUE:
    return "SECOND_VALUE";
  case NewEnumerableType::THIRD_VALUE:
    return "THIRD_VALUE";
  }
}

Wywołanie funkcji:

cout << NewEnumerableTypeToString(NewEnumerableType::SECOND_VALUE) << endl;

reference wrapper

Kontenery w C++ są właścicielami pamięci, obiektów w nich przechowywanych więc jeśli chcemy umieścić jakiś obiekt wewnątrz kontenera musi być znany jego rozmiar od razu, dlatego nie jest możliwe przechowywanie w wektorze typów polimorficznych przez wartość (klasy pochodne mogą być większe od typu bazowego, jednak wektor będzie rezerwował pamięć tylko o rozmiarze typu bazowego. Wywoływanie przesłoniętych funkcji na wartościach i tak nie ma sensu, bo nie zadziała polimorficzne wywołanie. Żeby polimorficzny typ działał poprawnie musi być on przechowywany przez referencję albo przez wskaźnik. Jednak wewnątrz wektora nie można umieścić referencji ponieważ wektor musi mieć możliwość przypisywania wartości po zadeklarowaniu elementów składowych a dla referencji jest to niemożliwe. Pozostaje jedyna możliwość - przechowywanie w wektorze typów polimorficznych przez wskaźnik (lub smart pointer).

W C++11 została dodana jednak jeszcze jedna możliwość reference_wrapper, która dla typu Base mogłaby być zaimplementowana w następujący sposób:

class BaseReferenceWrapper {
public:
  BaseReferenceWrapper(Base &base) 
    : base_{&base} {}
  //zdefiniowany operator rzutujący na referencję
  //przez co typu można używać jak referencji  
  operator Base&() { 
    return *base_; 
  }
private:
  Base *base_;
};

W tym momencie można zdefniować wektor przechowujący „refrencje”:

class Base {
public:
  virtual std::string foo() = 0;
};
 
class Derived : public Base {
public:
  std::string foo() override { reutrn "derived"; }
};
 
int main() {
  std::vector<std::reference_wrapper<Base>> bases;
  Derived d1;
  Derived d2;
  bases.emplace_back(d1);
  bases.emplace_back(d2);
 
  for (const Base &base : bases) {
    cout << base.foo() << endl;
  }
 
  return 0;
}

W bibliotece są też dodane dwie pomocnicze metody std::ref() i std::cref() tworzące odpowiednio reference_wrapper dla zmiennego i niezmiennego obiektu.

int main() {
  Derived d1;
  Derived d2;
 
  std::reference_wrapper<Derived> rd1 = std::ref(d1);
  std::reference_wrapper<Base> rb1 = std::ref<Base>(d1);
 
  std::cout << rd1.foo();
  std::cout << rb1.foo();
 
  std::reference_wrapper<const Derived> rd2 = std::cref(d2);
  std::reference_wrapper<const Base> rb2 = std::cref<Base>(d2);
 
 
  std::cout << rd1.foo(); //ERROR foo() nie jest const
  std::cout << rb1.foo(); //EROOR j.w.

Ćwiczenia

  1. [6 plusów] Zdefiniować interfejs Serializable zawierający pojedynczą metodę Serialize(Serializer *), każda klasa implementująca go zapisuje swój stan do obiektu Serializer pole po polu. Zdefiniować również dwie nowe klasy do projektu Academia: Room i Building. Obie klasy powinny implementować wspomniany interfejs. Zdefiniować również dwa różne formaty seraializacji: Serializatory XmlSerializer i JsonSerializer umożliwiające zapisywanie obiektów w formacie odpowiednio xml lub json. Zacząć testy od BaseSerializerTest, który nakłada warunki na bazową klasę Serializer.
  2. [6 plusów] Student wybiera się na juwenalia. Zaopatrzył się w plecak, i odpowiedni budżet. Student (akurat ten) jest niewybredny i nie ma dla niego większego znaczenia jakiej jakości prowiant kupuje. Ważne dla niego jest jednak to, żeby dotrwać do końca imprezy… Student napotyka jednak jeden z fundamentalnych problemów czasoprzestrzennych: zasobność plecaka jest ograniczona. Student napisał więc aplikację, która pozwoli mu optymalnie dobrać prowiant, tak, żeby z jednej stron nie zmarnować miejsca w plecaku, a z drugiej strony nie przeholować i uzyskać „założony stan zadowolonia imprezowego”: https://en.wikipedia.org/wiki/Blood_alcohol_content.
    1. Problem kolejny to taki, że student nie może wybierać z nieograniczonej liczby produktów, bo jest spóźniony na before-party. Wchodzi więc do sklepy i przebiega dział z prowiantem mijając 20 półek. Nie wie co leży na nich i biegnąc musi zdecydować, czy losowo wystawiony na półce produkt zabrać, czy nie ;)
    2. Po zakupach, student musi również uszeregowac prowiant w kolejności jego spożywania ;)
  3. [5 punktów] Dokończyć zadanie z Serializacją klas Building i Room i zdefiniowanymi serializatorami XmlSerializer i JsonSerializer. Zdefiniować również BuildingRespository repozytorium przechowujące listę wszystkich budynków, z możliwością zapisania stanu wszystkich jego obiektów do przekazanego w parametrze Serializatora (metoda: void StoreAll(Serializer *serializer) const). W docelowej aplikacji powinna się oczywiści znaleźć stowarzyszona metoda odczytująca zserializowane dane i wczytująca z pliku zapisany stan aplikacji, ale nie jest to już objętę pracą domową :) Repozytorium musi udostępniać też przeciążony operator[] tablicowy zwracający optional<Building> (std::experimental::optional w C++14)
pl/dydaktyka/jimp2/2017/labs/dziedziczenie2.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0