12 zut Advanced - Template, wyjatki, dziedziczenie

I - Templates
II - Wyjątki
III - Dziedziczenie

                                                                          I
                                                                      Template

Podstawowa budowa szablonu (Template) funkcji:

template<typename T>
T minmax(T a, T b, T C)
{
    if (a > c && a > b) return a;
   
}


wywołanie funkcji szablonowej:

minmax<int> (5,10,15);              //funkcja zwraca int

Przykład szablonu klasowego

#include<iostream>
#include<vector>
//using namespace std;

template<typename Char>
class String {
public:
String() {}                     //konstruktor

String(const std::string &a)    //konstruktor parametrowy
{
std:copy( a.begin(), a.end(), back_inserter(mojvector) );
}


String(String const &source)               //kopiujacy
{
mojvector=source.mojvector;
}


Char& operator[](int n)        //przeciazony operator []
{
return String::mojvector[n];
}


void operator+=(Char c)        //przeciazony operator +=
{
String::mojvector.push_back(c);
}


bool operator==(const String &rhs)        //przeciazony operator ==
{
if(mojvector==rhs.mojvector)
    return true;
else
    return false;
}

private:
std::vector<char> mojvector;           //Pole
};


int main()
{

String<char> a;    //tworzymy obiekt a
a+='a';            //przeciązonym operatorem += dodajemy elementy
a+='b';
a+='c';
a+='d';

std::cout<<a[2]<<std::endl; // wyswietlamy przeciazonym operatorem [] 2 znak

std::string dupa="hello";   // tworzymy string

String<char> b(dupa);       // tworzymy nowy obiekt z wykorzystaniem
                           // konstruktora parametrowego

if(a==dupa)                 // przeciazony operator ==
    std::cout<<"rowne"<<std::endl;
else
    std::cout<<"rozne"<<std::endl;

return 0;
}



Template może przyjmować jako argumenty typename i wtedy podajemy typ, ale moze przyjać zmienna.

Funkcja która pobiera wektor i inta i zwraca wektor podniesiony do potęgi w incie.

vector<int> powerof(const vector<int> &T, int T2)
{
vector<int> toRett = T;

for(int& element : toRett)
    element=std::pow(element,T2);
return toRett;
}
wywołanie
cel=powerof ( mojvektor , 2 );


to samo za pomocą template który określa tylko TYP

template<typename T> 
vector<T> powerof (const vector<T> &aa,int T2)
{
vector<T> temp=aa;

for(auto& element : temp)
    element=pow(element,T2);
return temp;
}
wywołanie
cel=powerof <int> (mojvektor , 2 );


albo template który określa typ i pobiera jednego inta

template<typename T,int T2>
vector<T> powerof (const vector<T> &aa)
{
vector<T> temp=aa;

for(auto& element : temp)
    element=pow(element,T2);
return temp;
}

wywołanie
cel=powerof <int, 2> (mojvektor);

Nie można przekazać wektora jako argument template (musi byc przekazany w nawiasie)
ale można przekazać TYP kontenera jako template:
Przykład template który pyta o dwa typy kontenera i przerzuca pomiędzy jednym a drugim:

template<typename T,typename T2, int N >
T2 powerof(const T& aa)
{
T2 retContainer;

for(auto& element : aa)
    retContainer.push_back(pow(element,N));

return retContainer;
}

wywołanie
vector<int> mojvektor {1,2,3,4,5,6,7,8,9};  //w {} to initializer list (co innego niz initialization list)
list      <int> cel;

cel=powerof <vector<int>, list<int> ,2 >( mojvektor );

Przyjmuje vektor, zwraca listę, podnosi do 2 potegi. Jak widać typ może być przekazany, wartość może być przekazana ale sam wektor już jako argument funkcji.
(w przypadku klasy kalo konstruktor argumentowy)



initializer list vs initialization list
This type is used to access the values in a C++ initialization list, which is a list of elements of type const T.

Objects of this type are automatically constructed by the compiler from initialization list declarations, which is a list of comma-separated elements enclosed in braces:

auto il = { 10, 20, 30 };  // the type of il is an initializer_list


Lokalizacja TEMPLATE w plikach 
Piszemy w pliku .H  czyli tam gdzie deklaracja klasy z prototypami, nie w plikach klasa.cpp czyli tam gdzie metody(funkcje)
Można wszystkie metody(funkcje) pisac w pliku H w ciele klasy, ale wtedy są inline (mają wspolną pamięć, szybsze ale wspolna pamiec przy duzej ilosci metod nie jest korzystna)


*THIS
Jeśli w klasie chcemy zwrocić referencję do niej samej to stosujemy:

*this   -    this to wskaźnik na klasę, zwracając referencję musimy podać jednak wartość nie                                wskaźnik, więc stosujemy *this.



Definicję funkcji można zdefiniować poza głównym template:

template<typename T>
struct X {
    void mf1() {};
    T mf2(); 
};

template<typename T>
T X<T>::mf2() { /* ... */ }



Dostęp do zmiennej zdefiniowanej w template

template<typename T>
struct Vector {
    static const int m1 = 7;
    static int m2 = 10;
    static int m3; 
};

template<typename T> int Vector<T>::m3 = 100;


Zadanie:
template Array<D,T,false>

D- wielkość jesli wybrany array
true - pracuje na vektorze, false - pracuje na array
T - typ danych
dostarcza interfejsy (operator [], begin(), end(),


#include<iostream>
#include<vector>
#include<array>
using namespace std;

template <int T,typename D,bool heap>
struct Array
{

Array()
{
}    
std::vector<D> mojav;
std::array<D,T> mojaa;
int it=0;


D& operator[] (int n)
{
if(heap)
    return mojav[n];
else
    return mojaa[n];
}

void operator+=(D zm)
{
if(heap)
    mojav.push_back(zm);
else
    mojaa[it++]=zm;
}


D& begin()
{
if(heap)
    return mojav.front();
else
    return mojaa.front();
}


D& end()
{
if(heap)
    return mojav.back();
else
    return mojaa.back();
}

};



int main()
{


Array<10,int,false> aaa;

aaa+=10;
aaa+=20;
aaa+=30;

std::cout<<aaa[2]<<std::endl;


return 0;
}


Zadanie:

Implement Matrix<N, M, T>

N - number of rows
M - number of columns
has templated copy construktor ( const Matrix<N1, M1, T1>& )
​N1 < N
M1 < M
T1 convertible to T

has operators:
​operator<<
operator[]
template operator+ (const Matrix<N1, M1, T1>)

is specialized for N = 0 && M = 0 (cannot be created)


#include<iostream>
#include<vector>
using namespace std;

template <int N,int M,typename T>
struct Matrix
{
std::vector<vector<T> > matryca {N,vector<T>(M)};         //macierz Vektorow o wielkosci NxM wypelniona zerami
std::vector<typename vector<T>::iterator> iteratory;      //vektor przechowujacy iteratory typu vector<T>
typename vector<vector<T> >::iterator it=matryca.begin(); //iterator 1 wymiaru macierzy, vektor na vektor


Matrix(){                                                 //konstruktor wypwlniajacy vektor iteratorw 2 wymiaru
if(N==0 && M==0)
    cout<<"N=0 i M=0, nie moge stworzyc"<<endl;
else
 for(int i=0;i<N;i++)
     iteratory.push_back((*(it+i)).begin());
}


template <int N1,int M1,typename T1>
Matrix (const Matrix<N1,M1,T1>& another){                        //konstruktor kopiujacy

if( std::is_convertible<T,T1>::value )
{
    if(N1<N && M1<M )
    {
        for(int i=0; i<N1;i++)
        {
            for( int j=0;j<M1;j++)
            {
            matryca[i][j]=another.matryca[i][j]; 
            }
        }
    }
    else
        cout<<"za duza tablica do skopiowania"<<endl;
}
else
    cout<<"niekonwertowalny TYP danych"<<endl;
}


template <int N1,int M1,typename T1>
void operator +(Matrix<N1,M1,T1> another)
{

if( std::is_convertible<T,T1>::value )
{
    if(N1<N && M1<M )
    {
        for(int i=0; i<N1;i++)
        {
            for( int j=0;j<M1;j++)
            {
            matryca[i][j]+=another.matryca[i][j]; 
            }
        }
    }
    else
        cout<<"za duza tablica do dodania"<<endl;
}
else
    cout<<"niekonwertowalny TYP danych"<<endl;
}



T operator[] (int n){                          //przeciazony operator [],podajesz zbiorowy index zwraca wartosc
int rz=(int)(n/M);
int nr=n-(rz*M);
return *(iteratory[rz]+nr);
}


friend ostream& operator<< (ostream& out, const Matrix& obj) {                         //przeciazony operator <<
out<<"start---------"<<endl;
out<<"Twoja matryca: "<<endl;

for(int i=0; i<N;i++)
{
    for( int j=0;j<M;j++)
    {
      out<<obj.matryca[i][j]; 
      out<<",";
    }
out<<endl;
}
out<<"end-----------"<<endl;
return out;
}

};


int main()
{
cout<<endl<<"Implementacja Matrix<N, M, T>  gdzie N i M wielkosc, T - typ"<<endl<<endl;
cout<<"Stworzenie Matrix<3,4,int> aaa i wypenie danymi."<<endl<<endl;

Matrix<3,4,int> aaa;                                      //stworz macierz vektorw o wielkosci 3x3

aaa.matryca[0][0]=10;
aaa.matryca[0][1]=11;
aaa.matryca[0][2]=12;
aaa.matryca[0][3]=77;

aaa.matryca[1][0]=13;
aaa.matryca[1][1]=14;
aaa.matryca[1][2]=15;
aaa.matryca[1][3]=88;

aaa.matryca[2][0]=16;
aaa.matryca[2][1]=17;
aaa.matryca[2][2]=18;
aaa.matryca[2][3]=99;


cout<<"Polecenie: 'cout << aaa' przeciazony <<  wynik:"<<endl;    // operator <<
cout<<aaa<<endl;

cout<<"Polecenie: 'cout << aaa[4]' przeciazony []  wynik:"<<endl;        //operator []
cout<<(aaa[4])<<endl;;
cout<<"end-----------"<<endl<<endl;


cout<<"Stworzenie Matrix<4,5,int> bb(aaa) za pomoca konstruktora kopiujacego."<<endl<<endl;;
Matrix<4,5,double> bb(aaa);         //konstruktor kopiujacy (tylko do wiekszej)

cout<<"Stworzona konstruktorem kopiujacym bb wyglada tak:"<<endl;   
cout<<bb<<endl<<endl;                     //wyswietl skopiowana 

cout<<"Dodaj bb+aaa, przeciazony + , wynik zapisz w bb."<<endl<<endl;

bb+aaa;

cout<<"Po dodaniu Twoje bb wyglada tak:"<<endl;   
cout<<bb<<endl<<endl;                     //wyswietl skopiowana 

return 0;
}



wynik:
Implementacja Matrix<N, M, T>  gdzie N i M wielkosc, T - typ

Stworzenie Matrix<3,4,int> aaa i wypenie danymi.

Polecenie: 'cout << aaa' przeciazony <<  wynik:
start---------
Twoja matryca:
10,11,12,77,
13,14,15,88,
16,17,18,99,
end-----------

Polecenie: 'cout << aaa[4]' przeciazony []  wynik:
13
end-----------

Stworzenie Matrix<4,5,int> bb(aaa) za pomoca konstruktora kopiujacego.

Stworzona konstruktorem kopiujacym bb wyglada tak:
start---------
Twoja matryca:
10,11,12,77,0,
13,14,15,88,0,
16,17,18,99,0,
0,0,0,0,0,
end-----------


Dodaj bb+aaa, przeciazony + , wynik zapisz w bb.

Po dodaniu Twoje bb wyglada tak:
start---------
Twoja matryca:
20,22,24,154,0,
26,28,30,176,0,
32,34,36,198,0,
0,0,0,0,0,
end-----------


Uwagi:
1) Skrócony sposób tworzenia vektora vektorów

std::vector<vector<T> > matryca {N,vector<T>(M)};


2) Jeśli chcesz stworzyć iterator na vektor ale nieznanego typu <T> (tu iterator 1 wymiaru matrycy)
dodaj typename:

typename vector<vector<T> >::iterator it=matryca.begin();


 jesli chcesz stworzyć vektor iteratorow  (iterator dla  typu vektor<T> )

std::vector<typename vector<T>::iterator> iteratory;

stworzenie i przypisanie do tego vektora  iteratorów (dlatego.begin) wewnętrznych vektorów- 2 wymiar macierzy

w konstruktorze:
for(int i=0;i<N;i++)
     iteratory.push_back( (*(it+i)).begin() );


3) Konstruktor kopiujący musi zawierac swoj template

template <int N1,int M1,typename T1>
Matrix (const Matrix<N1,M1,T1>& another){ 


4) Przeciążony operator << ma taka konstrukcje

friend ostream& operator<< (ostream& out, const Matrix& obj)


5) Jeśli chcesz sprawdzić czy typy są konwertowalne

std::is_convertible<T,T1>::value






SPECJALIZACJA templatów


jeśli mamy zwykłego template:

TEMPLATE<typname>
void print (T t){
cout<<t;}

to jeśli dodamy template bez typu

TEMPLATE<>
void print(K k)              to jeśli K jest konkretnym typem np struktura
{
cout<<k.wartosc;
}                                 to zostanie wybrane najlepsze dopasowanie i w przypadku typu K
                                   zostanie uruchomiona ta funkcja.



Zablokowanie typu wskaźnikowego



template<typename T>
class CustomString {
};

template<>                                                  Specjalizacja
class CustomString<int*> {                        typ wskaźnikowy
    CustomString() {
        static_assert("Pointer are not supported"); }        rzuci błąd podczas kompilacji
};



SFINALE -  linijka funkcji, jeśli ma błąd nie jest traktowana jak błąd, tylko funkcja jest nieotwierana
to zjawisko wykorzystuje Type-Traits

Link

przykład:
jak odrzucić wszystkie wskaźniki

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T, typename W = void>
struct CustomString{
};

template<typename T>
struct CustomString<T, 
        typename std::enable_if<std::is_pointer<T>::value>::type> 
{
    CustomString() { static_assert(true, "nope");}
};

int main(){
    CustomString<int> asd;
    CustomString<int*> asd1;
}

sprawdza czy pointer, jeśli tak, daje TRUE wtedy robi
::TYPE  - co jest błędem, zgodnie z SFINALE nie zgłasza błędu ale odrzuca linijkęfunkcji.



                                                                              II

Wyjątki


oddzielają logikę od zarządzania problemem
if elsy zabierają czas procesora.
w konstruktorze tylko wyjątki

kiedyś:
Error number, zwracane przez funkcję, lub zapisywane do zmiennej globalnej.

otwarcie pliku w funkcji - jak się wykrzaczy funkcji to nie zamknie pliku.
dlatego w C++ robimy obsługę plików w konstruktorze (otwarcie) i zamknięcie w destruktorze.

Exception to OBiekt który możemy rzucać. (w c++ możemy rzucać każdy obiekt nie tylko dziedziczący po EXCEPTION)

THROW - rzuca
TRY  - obszar ktory sprawdza
CATCH  - łapie

Jeśli rzucamy wyjątek w konstruktorze, to musimy sami pozamykać zaalokowane obszary bo destruktor nie zostanie uruchomiony.

Nie rzucać exception w Destruktorze, bo przerwie jego pracę!


Co rzucać?  najlepiej po STD::exception,  hierarchia klas

Możemy stworzyć własną klasę która dziedziczy po exception i dopisać


RETHROW
rzucanie jeszcze raz, żeby przekazać obsługę błędu dalej.
(można nawet ponownie wywołać main  :-)

catch  - od szczegółu do ogółu

try {
    // ...
} catch (user_defined_speciall_exception& e) {

} catch (bad_exception& e) {

} catch (exception& e) {

} catch (...) {

}



NOEXCEPT  

od c++11 - wtedy funkcja nie rzuca wyjątków


static exception ptr  nazwa  - i można rzucić z jednego do drugiego wątku.



                                                                           III

DZIEDZICZENIE
 visibility specifier


virtual functions()
virtual base classes (aka interfaces)
dynamic_cast, upcasting, downcasting, factory concept
vptr

generalnie mamy klasę bazową OSOBA:

struct Person {
    std::string name() const; 
    std::uint16_t age() const;
    std::uint_16_t height() const;

    virtual void fix() = 0;
};

pola imię, wiek wzrost są wspólne doa kobiet i mężczyzn:

struct Men : public Person {

    std::uint32_t beardLength() const;

    void fix() override; 
};


struct Women : public Person {
    std::pair<std::uint16_t, char> 
        breastSize() const;

    void fix() override;
};

wiec dokładają tylko swoje indywidualne zarost i biust.
Jest też metoda czysto wiertualna ( ta z =0 ) ktorą trzeba nadpisać.
Dodany na końcu override nie jest obowiązkowy, ale informuje nas to tym, że nadpisujemy.

Klasa z metodą czysto wirtualną jest klasą abstrakcyjną (nie ma obiektów)


Jak w klasie bazowej mamy konstruktor to w klasie dziedziczącej też MUSI być konstruktor (bo domyślny nie powstanie)

struct Car { 
    Car(EngineType et) : _et(et) {}
};

struct Sport : public Car {
    Sport(EngineType sport_et) : Car(sport_et) {}
};

Jeśli kolejne klasy dziedziczą od siebie to przy tworzeniu obiektów  Konstruktory są od środka (od bazowej)


Lista INICJALIZATORÓW  {  }
jak nie ma konstruktora to wypełnia po kolei pola klasy, ale silnie typowane (nie jak konstruktor)



PROBLEM ROMBU:
Kiedy mamy dwie klasy dziedziczące z bazowej a potem trzeci dziedziczący po tych dwóch następuje redundancja danych.
Rozwiązanie to Virtualne dziedziczenie:

struct Car : Vehicle {
    Weels w;
    MobilityType mt; // not needed
    virtual void move() noexcept override;
};

struct ElectricCar : virtual Car {
    Batteries b;
}; 

struct FuelCar : virtual Car {
    FuelType f;
}; 

struct Hybrid : FuelCar, ElectricCar  {
}; 



Virtual function dynamic binding

struct Base {
    virtual void f() {
        cout << "Base" << endl;
    }
    virtual ~Base() {
    }
};

struct Derived: Base {
    void f() override {
        cout << "Derived" << endl;
    }
    virtual ~Derived() {
    }
};



Base b;
Derived d;

b.f();
d.f();

Base bb = d;
bb.f();.

powyżej widać, że do stworzonego obiektu bb jest tylko PRZYRÓWNANY
więc wynik będzie
Base
Derived
Base
czyli obiekt bb wyświetli base bo on jest tego typu tylko pobiera dane z d



Przykład z referencję
Base b;
Derived d;

Base& rb = b;
Base& rd = d;

rb.f();
rd.f();

rb.Base::f();
rd.Base::f();

wynik powyzszego będzie:
Base
Derived
Base
Base

czyli wywołanie metody referencji na obiekt derived  rd z dopisanym typem klasy bazowej
czyli rd.Base::f() wywoła matodę bazową.

to samo wskazniki
Base b;
Derived d;

Base* pb = &b;
Base* pd = &d;

pb->f();
pd->f();

pb->Base::f();
pd->Base::f();

wynik powyzszego będzie:
Base
Derived
Base
Base


Dynamiczne castowanie

make_unique = new

struct Base {
    virtual ~Base() {
    }
};

struct Derived: Base {
    virtual void method() {
        cout << ".";
    }
};

void function() {
    vector<shared_ptr<Base>> v;
    v.push_back(make_shared<Base>(Base()));
    v.push_back(make_shared<Derived>(Derived()));

    for (auto e : v) {
        if (Derived* d = dynamic_cast<Derived*>(e.get())) {
            // can we call it
            // or downcast from shared_ptr to d successful
            d->method();
        }
    }
}

w vektorze obiekty typu bazowego i obiekty typu pochodnego

w warunku if dynamicznie castujemy, jeśli obiekt typu pochodnego przypisuje do wskaznika.

to samo ale range for na wyjątkach (i referencjach, referencja nie moze dostac stanu 0)


for (auto e : v) {
        try {
            Derived& d = dynamic_cast<Derived&>(*e.get());
            d.method();
        } catch (exception& e) {
            cout << "Exception: " << e.what() << endl;

        }



Przykład
struct Base {
    int a;
    Base(int _a) :
            a(_a) {
    }
};

struct Derived: Base {
    int b;
    Derived(int _a, int _b) :
            Base(_a), b(_b) {
    }
};

void foo() {
    Derived d1 { 1, 1 };
    Derived d2 { 2, 2 };
    Base& b_ref = d2;
    b_ref = d1;
    cout << d2.a << d2.b << endl;
}

co ważne b_ref=d1;  to nie przypisanie nowej referencji ale przypisanie pól jednego obiektu do drugiego

wynik  1 2
czyli b_ref  jest referencją do d2
do b_ref przyrównany (przepisze pola) d1  (z dwoma jedynkami )

ale zmieni tylko pierwszą jednykę !!! bo typ  Base ma tylko to pole i nie ma dostępu do zmiennej b





Funkcje virtualne - późne wiązanie, realizowane za pomocą V-table. Tylko jedna v-table per klasa nie per obiekt.
Nie da się wywołać virtualnych nadpisanych funkcji w konstruktorze i destruktorze bo v-table są tworzone po stworzeniu konst/destr.
Można to zrobić, jeśli rzutujemy swój typ na THIS.

Jeśli do statycznej metody (istniejącej na przestrzeni obiektów) dodamy this to odwołujemy się do metody konkretnego obiektu (obejście statyczności metody - patrz sposób wywoływania wątków POSIX w obiektach c++)
pthread musi wywoływać istniejącą metodę (statyczną , istnieje przed obiektem) dlatego używamy posiłkowej funkcji statycznej która w środku za pomocą THIS wywołuje konkretną funkcje/metodę konkretnego obiektu

void *drv::Ethernetdriverserver::work()  //docelowa funkcja do uruchomienia



void *drv::Ethernetdriverserver::RunWork(void *context)    //statyczna (static w hpp)
{
    return ( reinterpret_cast<drv::Ethernetdriverserver *>(context))->work();
}



//uruchomienie wątku za pomocą statycznej RunWork, 
// ostatni parametr "this" to argument przekazany do funkcji

pthread_create(&m_Thread_id,0,&RunWork,this)



czyli this, adres bierzącego obiektu jest przekazywany do RunWork jako parametr, tam kastowany jest na niego typ i uruchamiana docelowa, niestatyczna metoda docelowa work.

Typ sposobem za pomocą pomocnicznej statycznej metody uruchomiliśmy metode niestatyczną.




Wadą polimorfizmu jest wywoływanie funkcji virtualnych co powoduje skakanie po v-table i utratę trafności Cache.


FINAL - uniemożliwia dalsze dziedziczenie, zarówno metod jak i klas:

class Animal{
public:
    virtual void eat() const noexcept =0;
};

class Cat : public Animal{
public:
    virtual void eat() const noexcept final {
        cout << "I'm a cat and eating a rat." << endl;
    }
};

class HouseCat final: public Cat {
public:
    virtual void eat() const noexcept {
        cout << "I'm a house cat and eating Whiscas." << endl;
    }
};


OVERRIDE - nieobowiązkowe , ale jeśli dodamy to przypilnuje, że funkcja będzie musiała być nadpisana. Gdy ktoś zmieni nazwę funkcji w klasie bazowej to override zagwarantuje, że brak nadpisanej funkcji spowoduje, że się wywali:

class Animal{
public:
    virtual void eat() const noexcept =0;
};

class Cat : public Animal{
public:
    virtual void eat() const noexcept override {
        cout << "I'm a cat and eating a rat." << endl;
    }
};

class HouseCat: public Cat {
public:
    //virtual void eat() override {
    //    cout << "I'm a house cat and eating Whiscas." << endl;
    //}
};


WHY Virtual Destructor:

jeśli ktoś używa hierarchii klas polimorficznie to musi dodać virtualny destruktor.
Bez virtualnego, będzie dla klas pochodnych (typowanych klasą bazową) uruchamiać destruktor bazowy, czyli bez niszczenia pól dodanych.










git zadania:
https://github.com/JanosikOpryszek/kod/tree/master/ZUT/template

slajdy:
http://slides.com/uczelnia_bt_kw/

taczala git:
https://github.com/Super-Teachers

wilk git:
https://github.com/kwilkx/advc

syntex:
https://tohtml.com/cpp/