Operátorok újra, bővebben
Operátorok és osztályok
Ha egy operátor az adott osztály típust veszi át baloldali paraméterként (vagy unáris, egyparaméterű operátor), akkor az operátort az osztályon belül tagfüggvényként kezelhetjük. Ekkor az első ("bal oldali") paramétere automatikusan a this pointer lesz.
Copy assignment(értékadó operátor)
Vannak olyan esetek, amikor már egy kész objektumnak akarunk új értéket adni. Pl.
Ilyen esetekben egy értékadó operátor(copy assignment operator) hívásról beszélünk.
A másoló konstruktor testvére a copy assignment(értékadó) operator. A copy constructorhoz hasonlóan const T& -ként veszi át a másolandó objektumot és a default is generálódik belőle.
Fontos, hogy a copy assignment operátor nem új objektumot hoz létre így az előzőleg használt erőforrásokat fel kell szabadítani.
class DinTomb{
int* tomb;
std::size_t meret;
public:
/**
* @brief Default konstruktor, mindent 0-ra inicializál
*/
DinTomb() : tomb(nullptr), meret(0) {}
/**
* @brief Másoló konstruktor
* @param other a másik tömb amit másolunk
*/
DinTomb(const DinTomb& other) : tomb(other.tomb != nullptr ? new int[other.meret] : nullptr), meret(other.meret) {
for(std::size_t i = 0; i < other.meret; ++i){
tomb[i] = other.tomb[i];
}
}
/**
* @brief Értékadó operátor
* @param other a másik tömb amit másolunk
* @return referencia a tömbre aminek értéket adtunk
*/
DinTomb& operator=(const DinTomb& other){
if(this == &other) { //ha önmagát kapja paraméterül akkor nincs semmi teendő, ne vágjuk magunk alatt a fát
return *this; // *this -> this: pointer, akkor *this referencia(az objektumra amin a hívás történt)
}
delete[] tomb;
tomb = new int[other.meret];
meret = other.meret;
for(std::size_t i = 0; i < other.meret; ++i) {
tomb[i] = other.tomb[i];
}
return *this;
}
~DinTomb() {
delete[] tomb;
}
};
int main(){
}
Egyéb operátorok
Szeretnénk, hogy a tömbünkhöz a += operátorral is lehessen új elemet hozzáadni. Ehhez túl kell töltenünk += operátort.
A += operátorra "függvényként" az operator+= kifejezéssel hivatkozhatunk.
Nézzünk egy példát:
class DinTomb{
/*
...
*/
void operator+=(const T& elem){
push_back(elem); //delegáljuk a beillesztést a push_back függvénynek, nem duplikálunk kódot.
}
};
int main(){
DinTomb tomb;
tomb += 5.2; // értelmezzük: tomb.operator+=(5.2) -> operator+=(&tomb, 5.2)
return 0;
}
Friend
Most szeretnénk, ha a tömbünket ki is lehetne írni. Viszont ezzel van egy kis gond. Azt, hogy hova írjuk ki a tömböt(stdout, file, stb.) balértékként veszi át az operator<< (stream insertion operator), ezért ezt az osztályon kívük kell túltölteni.
A friend kulcsszó használatával az osztályon belül deklaráljuk a függvényt, ezzel "megengedjuk" neki, hogy a privát tagokat is lássa. Eztunán az osztályon kívül definiáljuk.
#include <iostream>
class DinTomb{
/*
...
*/
template<typename K>
friend std::ostream& operator<<(std::ostream& out, const DinTomb<K>& dtomb);
};
template<typename K>
std::ostream& operator<<(std::ostream& out, const DinTomb<K>& dtomb){
for(std::size_t i = 0; i < dtomb.meret; ++i){
out << dtomb.tomb[i] << ' ';
}
return out;
}
int main(){
DinTomb tomb;
tomb += 5.2; // értelmezzük: tomb.operator+=(5.2) -> operator+=(&tomb, 5.2)
tomb += 2.3;
tomb.push_back(8.7);
std::cout << tomb; //értsd: operator<<(std::cout, tomb);
return 0;
}
operator<<-t streamre való kiírásra használjuk, akkor mindig std::ostream& -et ad vissza és vesz át bal operandusként, valamint visszaadja a bal operandusát, így láncolhatóvá teszi az operátort. (std::cout << a << b << c;)
Természetesen ezt a példát friend nélkül is meg lehet oldani, azonban ez nem mindig van így.
Rule of 0/3
https://en.cppreference.com/w/cpp/language/rule_of_three
Rule of 3: Ha egy osztálynak szüksége van nem compiler-default destruktorra, másoló konstruktorra vagy copy assignment operátorra akkor majdnem biztosan szüksége van mindháromra.
advanced
Rule of 0: Azok az osztályok, amelyeknek nem compiler-default destructora, copy constructora, copy assignment operátora van, azok valamilyen explicit erőforrás-birtoklást valósítanak meg. (https://en.cppreference.com/w/cpp/language/rule_of_three). Ettől eltérő osztályoknak ne legyen nem compiler-default destruktora, copy constructora vagy copy assignment operátora.
Néhány kötöttség
Operátor túltöltéssel nem változtatható meg: * precedencia * asszocivitás
Ezen felül egyes operátoroknak csak kötött számú paramétere lehet.
A logikai(|| és &&) operátorok túltöltése esetén azok elvesztik a short-circuit tulajdonságukat.
Összehasonlító operátorok
Természetesen mindenféle más operátorokat, pl összehasonlító, ennek negáltja, nagyobb, kisebb, stb. is overloadolhatunk.
Pl. a tömbjeink összehasonlítása:
template <typename T>
class DinTomb{
T* tomb; //pointer a dinamikus tömbre
std::size_t meret; //a dinamikus tömb mérete
public:
//...
bool operator==(const DinTomb& other) {
if(meret != other.meret) {
return false;
}
for(std::size_t i = 0; i < meret; ++i) {
if(tomb[i] != other.tomb[i]) {
return false;
}
}
return true;
}
bool operator !=(const DinTomb& other) {
return !(*this == other);
}
};
Pl. indexelő operátor:
template <typename T>
class DinTomb{
T* tomb; //pointer a dinamikus tömbre
std::size_t meret; //a dinamikus tömb mérete
public:
//...
T& at(std::size_t idx);
const T& at(std::size_t idx);
T& operator[](std::size_t idx){
return at(idx); //delegáljuk a feladatot a már implementált at() tagfüggvénynek
}
const T& operator[](std::size_t idx) const{
return at(idx);
}
};