std::hex, std::setw, std::fill
Az előadás objektum bájtjait kiíró programjában...
Hogy lehet printf()
-fel megoldani a ciklusban lévő körülményes
kiírást?
Megoldás
printf("%02x ", ptr[i]);
A referencia mérete
Az előadás objektum bájtjait kiíró programjában...
Nem rossz ez a program? A sizeof(obj)
nem a referencia méretét
adja? Hiszen az obj
paraméter típusa konstans referencia. C-ben nem
volt szabad ilyet csinálni: ha indirekt módon, pointerrel adtunk át egy paramétert
(pl. tömböt), akkor a sizeof
mindig a pointer méretét adta.
Itt mi a helyzet?
Megoldás
Nincs olyan, hogy „referencia mérete”. Egy objektum referenciája, azaz neve,
mindenben megegyezik az eredeti objektummal, tökéletesen megkülönböztethetetlen
attól. Ezért sizeof(obj)==sizeof(T)
a függvényben. A referencia nem
objektum, és így nincs definiált mérete sem.
Érték kiírása
Az előadás objektum bájtjait kiíró programjában...
Működik az, ha nincs változó, csak egy értéket szeretnék kiírni a fenti függvénnyel?
Pl. helyes a print_obj_hex(1234);
sor? Miért?
Megoldás
Igen, működik. A konstans referencia típusú paraméterek át tudnak venni jobbértékeket is, tehát olyan értékeket, amelyek nem változóban vannak tárolva, nincs nevük. Ilyenkor a fordító a hívás idejére létrehoz egy ideiglenes változót, és beteszi az értéket, hogy a függvény referenciaként megkaphassa azt.
Struct és class
Mi a különbség a class
és a struct
között?
Megoldás
Csak annyi, hogy a class
-nál az adattagok (és az öröklések) alapértelmezetten
privátak, a struct
-nál publikusak. Ezért a struct Rectangle : Shape
egy
publikus leszármazást jelent.
Öröklés és tartalmazás
Ha az öröklésnél / tartalmazásnál is az objektum elejére kerül az ős objektum / tartalmazott objektum, akkor tulajdonképp mi a különség az öröklés és a tartalmazás között?
Megoldás
A memóriaképben ugyan semmi, de ez nem jelenti azt, hogy az öröklés és a tartalmazás között jelentésbeli különbség nincsen. Az öröklés „X egyfata Y”, a tartalmazás „X-nek van egy Y-ja” kapcsolatot fejez ki, és ez a programkód jelentése szempontjából sokat számít. Ettől függ az, hogy milyen konverziókat, paraméterátadásokat enged meg a fordító. Például nem származtathatunk egy pont típusból (x, y koordináták) téglalap típust azzal a céllal, hogy a téglalapnak legyen x és y koordinátája, mert akkor minden pontot váró függvénynek téglalapot is adhatunk paraméterként. A pont és a téglalap között nem leszármazási, hanem tartalmazási kapcsolat van: a téglalap nem egy pont, hanem a téglalapnak van egy pontja, amely a helyzetét meghatározza.
Offsetof I.
Az előadás programjában, a Az „objektum pointere plusz ofszet” kifejezés által egy pointer kapunk az adott adattagra.
Mivel a pont neve esetén az adattag típusa case TYPE_STRING
részben mit jelent
a char*
, és mit a char**
cast? Melyik miért ilyen?
Megoldás
char*
, az így kapott pointer char**
típusú. A (char*)
cast pedig a bájtonkénti címzés miatt kell.
Offsetof II.
Az előbb említett kódban, az adattagokat elérő, *(valami + valami.ofszet)
alakú kifejezések elég sok zárójelet
tartalmaznak. Elhagyható-e valamelyik zárójelpár, és miért?
Megoldás
((char*) &p)
– itt elhagyható a külső pár. A következő operátor
az összeadás, amely alacsonyabb precedenciájú, mint a (char*)
cast.
A sok zárójel
Tényleg kell a zárójel az előadás kódjában a
Nem, csak nagyon nehéz olvasni nélküle. A point_class_members[i].mptr
köré?
Megoldás
.*
operátor alacsonyabb precedenciájú,
mint a []
és .
operátorok, ezért a zárójel elhagyható lenne. Egy ilyen
kifejezésben viszont látszik, hogy a .*
jobb oldali operandusa egy teljes kifejezés
lehet, szemben a sima .
operátorral, amelynek a jobb oldali „operandusa” egy adattag
neve lehet, semmi más.
Keresztkérdések
Az alábbi elméleti kérdésekre szövegben válaszolj, esetleg pár soros kódrészlettel illusztrálva, ha úgy látod hasznosnak! (Megjegyzések: 1. Az is lehet, hogy értelmetlen valamelyik kérdés. 2. Saját véleményt, meglátást írj, ne guglizz :D)
- Ha egyetlen egy mondatban kell megfogalmaznod, hogy mi a közük C-ben a pointereknek a tömbökhöz, mi lesz az az egy mondat?
- Hány bájtos egy referencia?
- Létezik-e referenciák tömbje? Vajon miért?
std::optional
Néha szükségünk van egy olyan objektumra, amelyik egy jól meghatározott típusú értéket tárol – vagy éppenséggel üres. Például egy függvénynél, amelyik visszaad egy értéket, vagy nem ad vissza semmit. Ha sikerül beolvasnia egy számot, akkor visszaadja azt, ha nem, akkor pedig csak egy üres objektumot.
Ezt meg tudjuk oldani dinamikus memóriakezeléssel is. De ebben az esetben az kényelmetlen és lassú: egyetlen egy int
-et kellene dinamikusan foglalni, indirekten visszaadni stb. Ehelyett inkább egy olyan objektumra lenne szükségünk, amelyik ezt érték szerint tartalmazza, és tudja azt is, hogy üres-e vagy nem. A használatát így képzelhetjük el:
Optional<std::string> get_string_from_stdin();
int main() {
Optional<std::string> opt = get_string_from_stdin();
if (opt) {
std::cout << "Ezt a sztringet kaptam: " << *opt << std::endl;
} else {
std::cout << "Nem kaptam sztringet" << std::endl;
}
}
A tárolt érték típusa std::string
, de a sablonparaméter miatt lehetne bármi más.
Készíts egy ilyen osztályt! Ügyelj az alábbiakra:
- Nem használhatsz dinamikus memóriakezelést, mert akkor elveszik a feladat értelme.
- Helyette egy
sizeof(T)
méretű tömböt kell azOptional
objektumba tenni, és neked kell tudnod, hogy ott épp létre van hozva objektum vagy nincs. - Ügyelni kell arra is, hogy a tömb megfelelő módon helyezkedjen el a memóriában. Előfordulhat, hogy a
T
típusra vonatkoznak ilyen megkötések (pl.int
típus 4-gyel osztható memóriacímen). A memóriabeli igazítás azalignas
kulcsszóval oldható meg. - Az
Optional
objektum megszűntekor a tartalmazottT
destruktorát is meg kell hívni, már amennyiben volt tartalmazottT
.
Tesztesetek nincsenek megadva, azokat is neked kell kitalálnod. Vajon mi várható el egy ilyen osztálytól?
Gondolj arra, hogy egy Optional
0 vagy 1 darab objektumot tartalmaz érték szerint, és azt
az objektumot helyettesíti. Pl. másoláskor lemásolja azt. Érdemes a kódodat GCC/Clang -fno-elide-constructors
fordítási paraméter mellett tesztelni, hogy kikapcsold a hibákat elfedő optimalizációkat!
A C++17 óta egyébként van ilyen osztály, std::optional
néven. Ez a Boost optional
osztály mintájára készült.
Megoldás
#include <iostream>
template <typename T>
class Optional {
private:
alignas(T) unsigned char storage[sizeof(T)];
/* ha van benne objektum, ez == storage. ha nincs, akkor nullptr.
* lehetne egy bool is, de így egy csomó cast elkerülhető. */
T* obj;
public:
Optional() {
obj = nullptr;
}
Optional(T const & newdata) {
obj = new (storage) T(newdata);
}
~Optional() {
if (obj)
obj->~T();
}
Optional(Optional const & other) {
if (other.obj)
obj = new (storage) T(*other.obj);
else
obj = nullptr;
}
Optional& operator=(Optional const & other) {
if (this == &other)
return *this;
if (obj && !other.obj) {
obj->~T();
obj = nullptr;
} else if (!obj && other.obj) {
obj = new (storage) T(*other.obj);
} else if (obj && other.obj) {
*obj = *other.obj;
} else {
/* mindkettő uninitialized, nincs teendő */
}
return *this;
}
explicit operator bool () const {
return obj != nullptr;
}
T & operator*() {
if (!obj)
throw std::runtime_error("empty optional");
return *obj;
}
T const & operator*() const {
if (!obj)
throw std::runtime_error("empty optional");
return *obj;
}
};
Optional<std::string> get_word_from_stdin() {
std::string s;
if (std::cin >> s)
return s;
else
return {};
}
int main() {
Optional<std::string> opt = get_word_from_stdin();
if (opt) {
std::cout << "Ezt a szót kaptam: " << *opt << std::endl;
} else {
std::cout << "Nem kaptam szót" << std::endl;
}
}
Reflektivitás – a tevékenység virtuális függvényben
A kiindulási alap az extrákban megjelenő írás kódja.
A programot át lehetne írni úgy, hogy a kiírásokat végző dynamic_cast
sorozat helyett
a PointMptrBase
osztályba egy virtuális függvényt teszünk:
class PointMptrBase {
public:
virtual ~PointMptrBase() {}
virtual void save_to_stream(std::ostream & os, Point const& p) const = 0;
};
template <typename T>
class PointMptr : public PointMptrBase {
public:
PointMptr(T Point::* mptr) : mptr(mptr) {}
T Point::* mptr;
virtual void save_to_stream(std::ostream & os, Point const& p) const {
os << p.*mptr;
}
};
int main() {
/* ... */
for (int i = 0; point_class_members[i].name; ++i) {
std::cout << point_class_members[i].name << " = ";
point_class_members[i].mptrobj->save_to_stream(std::cout, p1);
std::cout << std::endl;
}
/* ... */
}
Mi a véleményed erről az átalakításról? Vannak előnyei, vannak hátrányai?
Megoldás
- Megszűnt a
dynamic_cast
, automatikusan megíródnak a függvények a sablonból. - Ha más működés kell bizonyos típusú adattagnál, egy adott
template <typename T> class PointMptr
osztály specializálható.
- A
PointMptrBase
osztályba bekerült egy olyan tevékenység, amihez annak az osztálynak nincs köze (az adattag kiírása). Ide kerülne az összes többi tevékenység is; pl. dialógusablak létrehozásakor a szövegbeviteli mező, csúszka stb. elemek létrehozása. Ezek nem igazán tartoznak ehhez az osztályhoz. Ez feladatspecifikus, nem tartozik egy reflektivitást biztosító keretrendszerhez.
Reflektivitás – dynamic_cast
A kiindulási alap az extrákban megjelenő írás kódja.
Mi a helyzet ezzel a dynamic_cast()
sorozattal, meg lehetne kerülni?
Megoldás
Meg, mint mindig. Betehetjük az MptrType<T>
típusokba a teendőket.
Esetleg használhatjuk a látogató mintát is,
hogy külön osztályba kerülhessenek.
Reflektivitás – Visitor
Ez is az az extrákban megjelenő írás kódjára vonatkozik.
Oldd meg látogató mintával (Visitor pattern) a reflektív objektumok bejárását, az egyes adattagokon elvégzendő tevékenységeket!
Megoldás
Nincs mintamegoldás, de ha csináltál egyet, küldd el :D