refined pointer and reference semantics
This commit is contained in:
parent
2e297f0769
commit
b04bc8736e
436
BASED.md
436
BASED.md
@ -132,13 +132,13 @@ A lényeg ez (kb. "kötelező" RAII + extra szabályok dangling ref / pointer el
|
||||
|
||||
- referenciákat lehet paraméterként megadni elvárt típusnak ÉS visszatérési értékként is metódusoknál
|
||||
- De referenciát eltárolni handle mezőben nem lehet - sima változóban félreteheted magadnak a stack-en ha nem akarod mindig kiírni az expression-t.
|
||||
- Pointerekre meg van egy ownership modell, hogy a handle-khez (class/ struct megfelelője) tartozik egy "resources" vagy "pointers" blokk... Valszeg csak külön syntax van erre, nem akarok ilyen hosszú szavakat kiírva látni, mert öli a produktivitást, hanem mint a C++ban pl van az inicializálós rész a konstruktornál, úgy a class-nál valami hasonló kis syntax erre...
|
||||
- Ugye pointereket vissza tudnak adni és el tudnak fogadni paraméterként @resource-nak és @release-nek jelölt függvények (mint pl. a malloc meg a free).
|
||||
- Pointerekre meg van egy ownership modell, hogy a handle-khez tartoznak a pointereik. Csak ők írhatják őket (más nem) - típus szerint ők írhatják, tehát más azonos típusú handle-nek is írhatom!
|
||||
- Ugye pointereket vissza tudnak adni és el tudnak fogadni paraméterként @resource-nak és @release-nek jelölt függvények (mint pl. a malloc meg a free). Ez egyszerűen kell az alap működéshez.
|
||||
- Az ilyen (így jelölt) függvényeket, csak konstruktorból és destruktorból tudod meghívni!!!
|
||||
- De persze a konstruktor beállíthat a pointerednek valami értéket - nem kell malloc-ból inicializálja meg! és ugye nem kell free-szerű dolgokat hívnia rá...
|
||||
- Meg persze a @resource nem kötelező, hogy pointer-t adjon vissza - mert adhat valami handle-t is mondjuk. Tehát ennek a "pointeres" blokknak vagy lehetnek nem pointeres változói, ami mondjuk egy integer csak, de úgy jobb, ha minden resource-t szerintem oda írsz - alternatíva, hogy tényleg csak a pointereket jelöljük külön is és akkor sima mezőnek ad értéket a konstruktorban a @resource-os függvény ami mondjuk egy adatbázis handle-t ad vissza valami hívásból és tárol el.... Lehet hogy tisztább ez így, csak így a resource handle-k nem egy helyen vannak, hanem félig a pointerek - félig a sima adatok közt. Valszeg érdemes pointer blokkot csinálni ami tényleg csak pointereket tartalmaz és ha nem használod - hát nem használod.... Egyre inkább hajlok az utóbbira, mert mégis tisztábbnak hangzik...
|
||||
- Meg persze a @resource nem kötelező, hogy pointer-t adjon vissza - mert adhat valami handle-t is mondjuk.
|
||||
- Nyilván ezek mind privátok: de ha valaki referenciát ad paraméternek a saját típusodból egy metódusodban, az abban lévőket eléred mert van rá ugye visibility-d úgy. Meg esetleg ugye csinálhatsz friend jellegű dolgot, vagy "owner" class-t aki eléri a pointereid. Esetleg ha egymásba ágyazott class-okat csinálhatsz, akkor ez default bekerül friend-ként, hogy a class törzsében definiált másik class alapból a kintit tekinti ownernek... Ez az "owner class" jó ötletnek tűnik - el is neveztem protectornak - és így lehetnek protected pointerek - na azokat éri el az a speciális class / handler!
|
||||
- A pointereknek adhatsz értéket, növelheted, csökkentheted, szorozhatod meg a faszomsetudja még mit akarsz vele. De csak a saját pointereidre van normális esetben láthatóságod...
|
||||
- A pointereknek adhatsz értéket, növelheted, csökkentheted, szorozhatod meg a faszomsetudja még mit akarsz vele. De csak a saját pointereidre van normális esetben láthatóságod... Tehát más pointerei amit kapsz azokat nem tudod változtatni se értékadás jobb oldalában szerepeltetve "lementeni" magadnak.
|
||||
- Alapból a pointer lehet null - alapból a referencia nem lehet null! Külön kell jelezni, ha lehet null egy visszaadott referencia, vagy paraméterben leírt ref - tehát ez a típus része. Mondjuk egy &ref? jellegű syntax, vagy valami ilyesmi.
|
||||
|
||||
^^ez szerintem majdnem mindent kezel, de nyilvánvalóan nem mindent és pont az a célom, hogy a rust-al ellentétben NE akarjak mindent safe-en tartani, csak minél többet...
|
||||
@ -163,6 +163,231 @@ Ez meg szerintem fasság - nem jó érvek:
|
||||
|
||||
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/Strandh-Tutorial/need-for-garbage-collection.html
|
||||
|
||||
## Jobb pointerek 1: safety
|
||||
|
||||
A pointerek ahogy fenn írtuk, alapvetően csak @resource és @release függvényekben menthetők és használhatók korlátlanul,
|
||||
és persze a handle-k adattagjai is lehetnek pointerek amiket akkor kezelünk, mert saját pointer állhat értékadás bal oldalán!
|
||||
|
||||
VISZONT! Kiadhatunk pointereket a user kód / caller felé, de számukra nem "menthetők" le ezek. Tehát alapból egy pointer
|
||||
nem lesz "menthető", de @resource-os és @release-es függvények esetén, @unsafe kódban és a saját ownership alatt IGEN!
|
||||
|
||||
Még egy fontos kivétel talán:
|
||||
|
||||
for (char* c = str; *c; ++c) {
|
||||
...
|
||||
}
|
||||
|
||||
while (char* c = file.nextline()) {
|
||||
...
|
||||
}
|
||||
|
||||
^^Ennél szerintem engedni kéne, hogy a for/while használhassa talán? De ez egy nagyon jó kérdés, mert speciális eset!
|
||||
Szerintem valahogy ez jó volna, mert praktikus baj nincs vele és C-interop miatt kellhet (főleg az utóbbi pl.)
|
||||
|
||||
## Jobb pointerek 2: speed
|
||||
|
||||
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
|
||||
Egy lehetőség, hogy tömbre (tehát ptr+méret-re)a default a restrict, de pointerre ki kell írni.
|
||||
|
||||
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
|
||||
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
|
||||
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
|
||||
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
|
||||
|
||||
Egy (talán elvetendő) ötlet: A pointer típusa lehessen csoportosítható:
|
||||
|
||||
handle A {
|
||||
int n;
|
||||
};
|
||||
|
||||
A@aliasinggroup1 variable = { 1 };
|
||||
A@aligrp2 array[5] = { 1, 2, 3, 4, 5 };
|
||||
|
||||
inline int magic(A *ptr1, A *ptr2) {
|
||||
ptr2->n = 42;
|
||||
return ptr1->n; // return 1 when inlined
|
||||
}
|
||||
|
||||
magic(&variable, &array[3]); // see that it cannot alias
|
||||
|
||||
Esetleg szükséges legyen ilyenkor a használati helyen is kiírni a típust?
|
||||
|
||||
inline int magic(A@aliasinggroup1 *ptr1, A@aligrp2 *ptr2);
|
||||
|
||||
Az a bajom ezzel... hogy "gyalázatosan ocsmány" sajnos ez mind...
|
||||
|
||||
Alternatíva:
|
||||
|
||||
* Tömbökről feltételezzük, hogy sosem alias-olnak be (hacsak nem "unrestrict" kulcsszavas).
|
||||
* Referenciák sem alias-olódnak
|
||||
|
||||
Alternatíva (all-in):
|
||||
|
||||
* Mindent restrict-nek veszünk by default és az unrestrict-et be kell írni...
|
||||
* Kicsit unsafe-nek hangzik és sok benne a meglepetés, de a perf jó.
|
||||
|
||||
Lásd FORTRAN: https://flang.llvm.org/docs/Aliasing.html
|
||||
|
||||
Alternatíva (typedef-szerű):
|
||||
|
||||
restrictptr PooledPtr int*;
|
||||
restrictptr RawPtr int*;
|
||||
|
||||
inline int magic(PooledPtr ptr1, RawPtr ptr2) {
|
||||
*ptr2 = 42;
|
||||
return *ptr1; // Should optimize as: return 2
|
||||
}
|
||||
|
||||
int i1 = 1;
|
||||
int i2 = 2;
|
||||
magic(&i1, &i2); // can optimize
|
||||
|
||||
Jelenleg ez az alternatíva tetszik a legjobban + persze a restrict kulcsszó támogatása emellett még pluszban!
|
||||
+ Talán lehetne restrictptr PooledArr int[]; jellegű típus definíció is (tömbök alias kerülésére)
|
||||
|
||||
Szemantika:
|
||||
|
||||
* Ha csak különböző ilyeneket látok egy paraméterlistán, akkor restrict-et kódgenerálunk mindre.
|
||||
* Ha csak különböző ilyeneket ÉS más típusú dolgokat... akkor is...
|
||||
* Egy ugyan ilyen típusú és egy másik ugyan ilyen típusú restricptr között viszont kell aliasing!
|
||||
* Egy (fenti példával élve) PooledPtr-es int* és egy sima int* között viszont nem kell!
|
||||
* Van továbbra is restrict kulcsszó (C-s szemantikával)
|
||||
|
||||
Ezekkel a szabályokkal egész jó dolgot csináltunk szerintem, mert ha valaki kiad egy pointert, akkor kiadhat
|
||||
hozzá ilyen plusz infókat, ami jelzi a fordítónak, hogy mik alias-olhatnak és mik nem. Egy jó példa, ha egy
|
||||
játék mondjuk ilyen memory arénákban tárol dolgokat és az "engine" tudhatja, hogy melyik arénából adjuk ki
|
||||
éppen a mutatót. Gyakorlatilag ez majdnem olyan, mintha valamit restrictptr-el deklarálunk, akkor annak
|
||||
az összes előfordulásánál beíródik a generált pointer elé a "restrict" - ez csak annyiban téves, hogy
|
||||
ha több ilyen is van egy függvényben / scope-ban azonos típusnéven, akkor azok egymással nem restrict-elnek!
|
||||
Ez utóbbit első körben implementálhatjuk úgy, hogy ilyenkor lekerül a restrict, de úgy is, hogy kódgenerálással
|
||||
megoldjuk csak a kettő közötti restrict-álást és fenn hagyjuk a kulcsszót. Ez egy kicsit bonyolítja a dolgokat...
|
||||
|
||||
Az ilyen pointer cast-olható az alaptípusára - de ezzel elveszítjük a speed boost lehetőségét olyankor!
|
||||
Megj.: Szerintem ezzel generálható C-kód -fno-strict-aliasing mellett is akár, ha típus alapút nem akarok!
|
||||
|
||||
## Jobb referenciák
|
||||
|
||||
* A referenciának lehetne "értéket adni" - nem mint C++ban...
|
||||
* De ahogy fentebb írtuk másolni csak lokális változóba lehet a stackre
|
||||
* A referencia "lényegében egy pointer, de nincs rajta aritmetika"... szóval nem tömböt jelképez.
|
||||
* Kérdőjel hozzáadásával jelölhető, hogy null lehet-e (default: nem).
|
||||
|
||||
## Jobb tömbök
|
||||
|
||||
Néhány dologra van szerintem szükségünk:
|
||||
|
||||
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
|
||||
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
|
||||
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
|
||||
|
||||
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
|
||||
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
|
||||
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
|
||||
meg kellene oldani...
|
||||
|
||||
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
|
||||
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
|
||||
|
||||
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
|
||||
|
||||
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
|
||||
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
|
||||
|
||||
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
|
||||
|
||||
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
|
||||
|
||||
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
|
||||
|
||||
Lásd még:
|
||||
|
||||
https://youtu.be/MUISz2qA640?t=1980
|
||||
|
||||
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
|
||||
|
||||
### restrict-ált tömbök
|
||||
|
||||
A tömbökre is kell a pointerekhez hasonló restrict.
|
||||
|
||||
## Típusos indexek
|
||||
|
||||
Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá!
|
||||
|
||||
struct A { uint32_t a, b, c; }; // 3*4=12 bytes with 4 byte alignment
|
||||
struct B { child *a, *b, *c; }; // 3*8=24 bytes with 8 byte alignment
|
||||
|
||||
struct C { A aa; uint32_t x; }; // 4*4=16 bytes with 4 byte alignment
|
||||
struct D { B bb; uint32_t x; }; // 4*8=32 bytes with 8 byte alignment (4 wasted bytes)
|
||||
|
||||
Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Viszont miért ne lehetnének?
|
||||
|
||||
child@uint32_t index; // Például így
|
||||
|
||||
A syntax-on lehetne még variálni...
|
||||
|
||||
De az a helyzet, hogy erre már van megoldás jelenleg is (sőt C-ben is, struct-al):
|
||||
|
||||
record ChildIndex {
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
esetleg még jobb, ha "like-olást" is használunk közben:
|
||||
|
||||
record child_index {
|
||||
like uint32_t index;
|
||||
};
|
||||
|
||||
### NonOwningPointer - smartpointer
|
||||
|
||||
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
|
||||
|
||||
class Owner {
|
||||
private:
|
||||
std::vector<int> vec = {1, 2, 3};
|
||||
|
||||
// Has a vector of pointers to children
|
||||
// So when this go out of scope we can set child pointers backpointers to nulls (indicating non-living)
|
||||
OwnPtrFactory<int> fac;
|
||||
|
||||
public:
|
||||
// or access(..) etc.
|
||||
// NownPtr has a pointer to the factory's own elem and on its move assign / move constructor calls updates facs child vec's
|
||||
// pointer. Caller can ask the pointer if owner is already deleted or not. Maybe should be threadsafe?
|
||||
NownPtr<int> getMid() {
|
||||
return fac.create(&vec[1]);
|
||||
}
|
||||
};
|
||||
|
||||
Ezt követően a használati ponton olyasmi történik, hogy:
|
||||
|
||||
class User {
|
||||
void f(..) {
|
||||
...
|
||||
// Safe használat
|
||||
NownPtr mid = owner.getMid();
|
||||
mid.if_exists([](int &i) {
|
||||
... i is accessible ...
|
||||
});
|
||||
|
||||
// Unsafe használat (esetleg nálam @unsafe és konstruktor/destruktor esetben?)
|
||||
int j = *mid + 40;
|
||||
|
||||
// Unsage-nél is lehessen kérdezni
|
||||
if(mid) {
|
||||
int k = *mid;
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
Ez tehát úgy látom C++ nyelven is lehetséges, egyedül mozgatáskor tűnik kicsit is "lassabbnak" meg ugye ez egy "fat" pointer, ami
|
||||
2x pointernyi méretet használ fel... az egyik maga a mutatott terület címe, a másik a "factory"-ban lévő vektor megfelelő elemére
|
||||
mutat rá - itt vigyázni kell: a vektor resize esetén invalidálódik ez! Kéne valami "stabil-vektor" ami blokkos és a resize-nál NEM
|
||||
invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
|
||||
|
||||
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
|
||||
|
||||
## Custom allokátorok / arénák
|
||||
|
||||
A go-hoz újabban ajánlott "arénás" modell-t is lehet hogy valahogy alkalmazhatnánk - tehát hogy azzal oldjuk meg a custom allokátor kérdést... De szerintem egyszerűen úgy kell kialakítani a standard libet / kódokat, hogy template paraméter szerűen átvegye milyen @resource és @release függvényeket használ - tehát nem konkrétan malloc-ot, meg free-t mondjuk hanem csak azoknak megfelelő deklarációs függvényeket... Ezt C-ben is szoktam csinálni (pl. a kismap-ban van ilyen), de ugye ott függvény pointerekkel, amiket vagy eltárolok (de akkor runtime költsége van), vagy ha a perf fontos, akkor pl. a kismap esetén azt szoktam, hogy ezeket nem csak konstruálásnál, hanem minden hívásnál át kell adni ami használja - azért mert látom, hogy a compiler be tudja inline-olni, ha egy static inline force-inline stb. függvényt adok át neki function pointernek... De ez ugye "trükközgetés" feleslegesen és jó lenne, ha nyelvi elemként tudnánk ezt. Esetleg érdemes a hagyományos generik / template témától ezt különszedni? Nem biztos, lehet hogy azzal kezelendő. Cpp esetén template-ekkel szoktam ezt csinálni...
|
||||
@ -294,209 +519,6 @@ Lásd:
|
||||
|
||||
Ez szerintem low-level helyekre jó lehet... Ha jól definiálom mi történik, akkor padding / align helyett (mellett) is talán.
|
||||
|
||||
## Jobb pointerek
|
||||
|
||||
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
|
||||
Egy lehetőség, hogy tömbre (tehát ptr+méret-re)a default a restrict, de pointerre ki kell írni.
|
||||
|
||||
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
|
||||
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
|
||||
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
|
||||
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
|
||||
|
||||
Egy (talán elvetendő) ötlet: A pointer típusa lehessen csoportosítható:
|
||||
|
||||
handle A {
|
||||
int n;
|
||||
};
|
||||
|
||||
A@aliasinggroup1 variable = { 1 };
|
||||
A@aligrp2 array[5] = { 1, 2, 3, 4, 5 };
|
||||
|
||||
inline int magic(A *ptr1, A *ptr2) {
|
||||
ptr2->n = 42;
|
||||
return ptr1->n; // return 1 when inlined
|
||||
}
|
||||
|
||||
magic(&variable, &array[3]); // see that it cannot alias
|
||||
|
||||
Esetleg szükséges legyen ilyenkor a használati helyen is kiírni a típust?
|
||||
|
||||
inline int magic(A@aliasinggroup1 *ptr1, A@aligrp2 *ptr2);
|
||||
|
||||
Az a bajom ezzel... hogy "gyalázatosan ocsmány" sajnos ez mind...
|
||||
|
||||
Alternatíva:
|
||||
|
||||
* Tömbökről feltételezzük, hogy sosem alias-olnak be (hacsak nem "unrestrict" kulcsszavas).
|
||||
* Referenciák sem alias-olódnak
|
||||
|
||||
Alternatíva (all-in):
|
||||
|
||||
* Mindent restrict-nek veszünk by default és az unrestrict-et be kell írni...
|
||||
* Kicsit unsafe-nek hangzik és sok benne a meglepetés, de a perf jó.
|
||||
|
||||
Lásd FORTRAN: https://flang.llvm.org/docs/Aliasing.html
|
||||
|
||||
Alternatíva (typedef-szerű):
|
||||
|
||||
restrictptr PooledPtr int*;
|
||||
restrictptr RawPtr int*;
|
||||
|
||||
inline int magic(PooledPtr ptr1, RawPtr ptr2) {
|
||||
*ptr2 = 42;
|
||||
return *ptr1; // Should optimize as: return 2
|
||||
}
|
||||
|
||||
int i1 = 1;
|
||||
int i2 = 2;
|
||||
magic(&i1, &i2); // can optimize
|
||||
|
||||
Jelenleg ez az alternatíva tetszik a legjobban + persze a restrict kulcsszó támogatása emellett még pluszban!
|
||||
+ Talán lehetne restrictptr PooledArr int[]; jellegű típus definíció is (tömbök alias kerülésére)
|
||||
|
||||
Szemantika:
|
||||
|
||||
* Ha csak különböző ilyeneket látok egy paraméterlistán, akkor restrict-et kódgenerálunk mindre.
|
||||
* Ha csak különböző ilyeneket ÉS más típusú dolgokat... akkor is...
|
||||
* Egy ugyan ilyen típusú és egy másik ugyan ilyen típusú restricptr között viszont kell aliasing!
|
||||
* Egy (fenti példával élve) PooledPtr-es int* és egy sima int* között viszont nem kell!
|
||||
* Van továbbra is restrict kulcsszó (C-s szemantikával)
|
||||
|
||||
Ezekkel a szabályokkal egész jó dolgot csináltunk szerintem, mert ha valaki kiad egy pointert, akkor kiadhat
|
||||
hozzá ilyen plusz infókat, ami jelzi a fordítónak, hogy mik alias-olhatnak és mik nem. Egy jó példa, ha egy
|
||||
játék mondjuk ilyen memory arénákban tárol dolgokat és az "engine" tudhatja, hogy melyik arénából adjuk ki
|
||||
éppen a mutatót. Gyakorlatilag ez majdnem olyan, mintha valamit restrictptr-el deklarálunk, akkor annak
|
||||
az összes előfordulásánál beíródik a generált pointer elé a "restrict" - ez csak annyiban téves, hogy
|
||||
ha több ilyen is van egy függvényben / scope-ban azonos típusnéven, akkor azok egymással nem restrict-elnek!
|
||||
Ez utóbbit első körben implementálhatjuk úgy, hogy ilyenkor lekerül a restrict, de úgy is, hogy kódgenerálással
|
||||
megoldjuk csak a kettő közötti restrict-álást és fenn hagyjuk a kulcsszót. Ez egy kicsit bonyolítja a dolgokat...
|
||||
|
||||
Az ilyen pointer cast-olható az alaptípusára - de ezzel elveszítjük a speed boost lehetőségét olyankor!
|
||||
Megj.: Szerintem ezzel generálható C-kód -fno-strict-aliasing mellett is akár, ha típus alapút nem akarok!
|
||||
|
||||
## Jobb referenciák
|
||||
|
||||
* A referenciának lehetne "értéket adni" - nem mint C++ban...
|
||||
* A referencia "lényegében egy pointer, de nincs rajta aritmetika"...
|
||||
* Kérdőjel hozzáadásával jelölhető, hogy null lehet-e (default: nem).
|
||||
|
||||
## Jobb tömbök
|
||||
|
||||
Néhány dologra van szerintem szükségünk:
|
||||
|
||||
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
|
||||
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
|
||||
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
|
||||
|
||||
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
|
||||
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
|
||||
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
|
||||
meg kellene oldani...
|
||||
|
||||
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
|
||||
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
|
||||
|
||||
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
|
||||
|
||||
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
|
||||
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
|
||||
|
||||
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
|
||||
|
||||
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
|
||||
|
||||
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
|
||||
|
||||
Lásd még:
|
||||
|
||||
https://youtu.be/MUISz2qA640?t=1980
|
||||
|
||||
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
|
||||
|
||||
### restrict-ált tömbök
|
||||
|
||||
A tömbökre is kell a pointerekhez hasonló restrict.
|
||||
|
||||
## Típusos indexek
|
||||
|
||||
Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá!
|
||||
|
||||
struct A { uint32_t a, b, c; }; // 3*4=12 bytes with 4 byte alignment
|
||||
struct B { child *a, *b, *c; }; // 3*8=24 bytes with 8 byte alignment
|
||||
|
||||
struct C { A aa; uint32_t x; }; // 4*4=16 bytes with 4 byte alignment
|
||||
struct D { B bb; uint32_t x; }; // 4*8=32 bytes with 8 byte alignment (4 wasted bytes)
|
||||
|
||||
Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Viszont miért ne lehetnének?
|
||||
|
||||
child@uint32_t index; // Például így
|
||||
|
||||
A syntax-on lehetne még variálni...
|
||||
|
||||
De az a helyzet, hogy erre már van megoldás jelenleg is (sőt C-ben is, struct-al):
|
||||
|
||||
record ChildIndex {
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
esetleg még jobb, ha "like-olást" is használunk közben:
|
||||
|
||||
record child_index {
|
||||
like uint32_t index;
|
||||
};
|
||||
|
||||
### NonOwningPointer - smartpointer
|
||||
|
||||
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
|
||||
|
||||
class Owner {
|
||||
private:
|
||||
std::vector<int> vec = {1, 2, 3};
|
||||
|
||||
// Has a vector of pointers to children
|
||||
// So when this go out of scope we can set child pointers backpointers to nulls (indicating non-living)
|
||||
OwnPtrFactory<int> fac;
|
||||
|
||||
public:
|
||||
// or access(..) etc.
|
||||
// NownPtr has a pointer to the factory's own elem and on its move assign / move constructor calls updates facs child vec's
|
||||
// pointer. Caller can ask the pointer if owner is already deleted or not. Maybe should be threadsafe?
|
||||
NownPtr<int> getMid() {
|
||||
return fac.create(&vec[1]);
|
||||
}
|
||||
};
|
||||
|
||||
Ezt követően a használati ponton olyasmi történik, hogy:
|
||||
|
||||
class User {
|
||||
void f(..) {
|
||||
...
|
||||
// Safe használat
|
||||
NownPtr mid = owner.getMid();
|
||||
mid.if_exists([](int &i) {
|
||||
... i is accessible ...
|
||||
});
|
||||
|
||||
// Unsafe használat (esetleg nálam @unsafe és konstruktor/destruktor esetben?)
|
||||
int j = *mid + 40;
|
||||
|
||||
// Unsage-nél is lehessen kérdezni
|
||||
if(mid) {
|
||||
int k = *mid;
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
Ez tehát úgy látom C++ nyelven is lehetséges, egyedül mozgatáskor tűnik kicsit is "lassabbnak" meg ugye ez egy "fat" pointer, ami
|
||||
2x pointernyi méretet használ fel... az egyik maga a mutatott terület címe, a másik a "factory"-ban lévő vektor megfelelő elemére
|
||||
mutat rá - itt vigyázni kell: a vektor resize esetén invalidálódik ez! Kéne valami "stabil-vektor" ami blokkos és a resize-nál NEM
|
||||
invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
|
||||
|
||||
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
|
||||
|
||||
## Holy-C féle kiegészítések a switch statement-hez
|
||||
|
||||
Erről még nem vagyok meggyőzve, de néha hasznosnak tűnnek:
|
||||
|
Loading…
x
Reference in New Issue
Block a user