Indexelő objektum
Írj egy osztályt, amely egy 32 bites, előjel nélküli egészben: uint32_t (#include <cstdint>) tud
tárolni 32 igaz/hamis értéket! Az objektumot lehessen indexelni, és indexelésen keresztül valamelyik bit értékét lekérdezni és
beállítani!
BoolArray b; /* 32 db false */
std::cout << b[3]; /* 0 = false */
b[3] = true;
std::cout << b[3]; /* 1 = true */
A nehézséget itt az jelenti, hogy a biteket tároló egész szám egyes bitjeire nem létezik referencia, és emiatt az indexelő
operátor nem térhet vissza egyszerűen egy bool& típussal. Helyette egy segédobjektummal kell visszatérnie, amely
saját típussal rendelkezik, és amely a lekérdezést és az értékadást megfelelően átdefiniált operátorokkal kezelni tudja.
Ha már működik a fenti, oldd meg, hogy a bitek közötti értékadás is működjön!
b[3] = true;
b[2] = b[3];
std::cout << b[2]; /* 1 = true, mert b[3] = true */
Indexelő objektum – hibás gondolatmenet, de működik?
Adott az előző feladatnak az alábbi megoldása. A kód csak a segédobjektumot mutatja vázlatosan, jelenleg csak ez a lényeges rész.
class BitArray::Proxy {
private:
BitArray &ba;
int idx;
public:
Proxy(BitArray &ba, int idx): ba(ba), idx(idx) {}
operator bool () const {
return ba.get(idx);
}
Proxy& operator=(bool b) {
ba.set(idx, b);
return *this;
}
Proxy& operator=(Proxy &other) = delete;
};
Ez egy hibás elgondolást mutat be. Mi az? És hogyan lehetséges, hogy a Proxy mégis működőképes így megvalósítva?
Megoldás
A hibás elgondolás az, hogy ennek törölve van az értékadó operátora. Ugyanis a két Proxy közötti értékadás
lehetséges: bitarray[0] = bitarray[1], ennek működnie kell. Az első bitet kell másolnia a nulladik bitbe,
még akkor is, ha forráskód szintjén ez a Proxy objektumok értékadásának látszik.
A problémát azért oldja meg mégis a delete-es sor (valószínűleg nem szándékosan), mert az nem csak azt fejezi ki a
fordítónak, hogy nem hívható az értékadó operátor, hanem azt is, hogy a paraméterezése a szokásostól eltérő. Észre kell venni, hogy
ebben a sorban nincs const szó; a paraméter Proxy &other nem konstans referencia. Vagyis ez így két
dolgot jelent: a) ennek az osztálynak nem konstans objektumot vár az értékadó operátora, b) és mellesleg nem használható.
A bitarray[0] = bitarray[1] sorban két Proxy keletkezik, és mindkettő temporális objektum. A
referencia típusa a jobb oldali, bitarray[1] temporális objektum miatt lényeges; a nem konstans referencia nem vehet
át temporális objektumot paraméterként. Ezért a fordító számításba sem veszi azt az operátort, amikor ezt az értékadást
megpróbálja feloldani. Helyette konverziót keres, és rátalál az operator bool + operator=(bool) párosra,
ami pedig épp azt csinálja, amit kell. A delete-elésnek nincs is jelentősége, működne anélkül is, egy kósza
deklarációval.
Telítéses aritmetika
Írj osztályt, amely telítéses aritmetikát (saturation arithmetic) valósít meg!
Ebben a tárolt számok értékének van egy minimális és maximális értéke. Túlcsordulás (vagy alulcsordulás)
esetén a határon a kapott érték sosem lép túl. Például ha az alsó határ 0, akkor 10-5 = 5, de 5-7 = 0.
Vagy ha a felső határ 255, akkor 250 + 7 = 255.
A digitális jelfeldolgozásban (pl. hangfeldolgozásban) használt eszközök működnek így.
Számrendszerek
Nézd meg az extrákban az I/O manipulátorokkal kapcsolatos írást! Ez az előadás mértékegységes példájának folytatása. Utána...
a) Írj bináris szám kiíró I/O manipulátort! A használata legyen az alábbi:
std::cout << bin << 0 << std::endl; /* 0 */
std::cout << bin << 240 << std::endl; /* 11110000 */
Vajon mi lehet itt a bin típusa?
b) Írj akárhányas számrendszerben kiíró I/O manipulátort!
std::cout << base(2) << 240 << std::endl; /* 11110000 */
std::cout << base(7) << 240 << std::endl; /* 462 */
std::cout << base(16) << 240 << std::endl; /* F0 */
Melyik az első részkifejezés, amit ki kell értékelnie a fordítónak? Indulj ki ebből!
Ügyelj arra, hogy a megvalósításban ne legyen pow() (egész számokkal kell dolgozni, a
pow() pedig lebegőpontos számokkal dolgozik, pontatlanság lehet). Ezen kívül, ne legyen
std::to_string() se! Legalábbis egyetlen számjegy sztringgé alakítására semmiképp, mivel
akkor minden számjegyhez külön std::string objektum jönne létre.
Mértékegységek
Folytasd az előadás mértékegységes programját!
Mekkora a Ferrari motorjának átlagos teljesítménye a programban megadott gyorsítás közben? (A mozgási energiát az E=1/2*m*v2 képletből tudod kiszámolni; ennyi energiát adott le a motor 4 másodperc alatt.)
A mértékegység osztály dimenzió nélküli változata (m0*kg0*s0) egy egyszerű valós számot ad meg, amely általában egy arányt jelképez. Például a hossz/hossz egy dimenzió nélküli számot ad. Specializáld úgy az osztályt, hogy a dimenzió nélküli mértékegységek konvertálhatók legyenek valós számmá, illetve automatikusan létrehozhatók legyenek valós számból!
Az előző feladat megvalósítása után, működnek-e automatikusan a
valós*dimenziónélküli(pl.2.0*(v1/v2)), és avalós+dimenziónélküli(pl.1.0+(v1/v2)) alakú kifejezések? Miért?
approx()
A pytest API tartalmaz egy approx()
nevű függvényt, amelyikkel valós számok „nagyjából” egyenlőségre vizsgálhatóak:
0.1 + 0.2 == approx(0.3)
Oldd meg, hogy C++-ban is lehessen ilyet írni!
constexpr minősítésű _binary
Meg lehet oldani, hogy a _binary utótag operátor constexpr
legyen? Ehhez az kell, hogy az utótag operátor függvénye egyetlen return utasításból
álljon. (Egy ügyesen megírt rekurzív segédfüggvénnyel próbálkozz!)
Megoldás
Az alábbi, binary_val függvény két részre bontja az átalakítandó
számot. A szám már átalakított, bal oldali része a left, a maradék,
át még nem alakított része a right paraméterben van. Pl. az 1101
számnál az első számjegy átalakítása után left=1, right="101"
paraméterekkel hívódik a függvény. Egy lépésben egy számjegy átalakítása történik meg, ehhez a left-et
eggyel balra kell léptetni, és beírni a right következő számjegyét:
left=11, right="01" értékekkel hívni
újra a függvényt. Ha right üres, nincs teendő.
constexpr unsigned long long binary_val(unsigned long long left, char const *right) {
return right[0] == '\0'
? left
: binary_val(left<<1 | (right[0]-'0'), right+1);
}
constexpr unsigned long long operator "" _binary(char const *str) {
return binary_val(0, str);
}
A binary_val() törzse egy return utasításból áll, ezért
constexpr lehet. Az operator "" _binary() törzse egy
return utasításból áll, és csak constexpr függvényt hív,
ezért ez is lehet constexpr.
Dátum literálisok I.
Date d = "2013.08.11"_date;
std::cout << d << std::endl; /* 2013.08.11. */
std::cout << "'99.08.11"_date << std::endl; /* 1999.08.11. */
std::cout << "'12.08.11"_date << std::endl; /* 2012.08.11. */
std::cout << "08.11.2013"_date << std::endl; /* 2013.08.11. */
Dátum literálisok II.
A feladat ugyanaz, mint az előbb – de fordítási időben kell megoldani, constexpr
függvényekkel.
Ha minden rendben van, akkor egy helyes megoldás esetén lehet
"'99.08.11"_date.getYear() méretű globális tömböt létrehozni, vagy
ezt a számot (1999) sablonparaméternek használni. Ezek persze értelmetlen példák,
csak ötletként szerepelnek itt az ellenőrzésre, hogy tényleg fordítási időben fut-e
a dátum értelmezése. A lényeg az, hogy mivel literálisról van szó, azt már fordítás
közben is lehet (és érdemes) értelmezni, mert úgysem változik futás közben.