Devadesátková hra Brány Skeldalu prošla portací a je dostupná na platformě Steam. Vyšel i parádní blog autora o portaci na moderní systémy a platformy včetně Linuxu.
Lidi dělají divné věci. Například spouští Linux v Excelu. Využít je emulátor RISC-V mini-rv32ima sestavený jako knihovna DLL, která je volaná z makra VBA (Visual Basic for Applications).
Revolut nabídne neomezený mobilní tarif za 12,50 eur (312 Kč). Aktuálně startuje ve Velké Británii a Německu.
Společnost Amazon miliardáře Jeffa Bezose vypustila na oběžnou dráhu první várku družic svého projektu Kuiper, který má z vesmíru poskytovat vysokorychlostní internetové připojení po celém světě a snažit se konkurovat nyní dominantnímu Starlinku nejbohatšího muže planety Elona Muska.
Poslední aktualizací začal model GPT-4o uživatelům příliš podlézat. OpenAI jej tak vrátila k předchozí verzi.
Google Chrome 136 byl prohlášen za stabilní. Nejnovější stabilní verze 136.0.7103.59 přináší řadu novinek z hlediska uživatelů i vývojářů. Podrobný přehled v poznámkách k vydání. Opraveno bylo 8 bezpečnostních chyb. Vylepšeny byly také nástroje pro vývojáře.
Homebrew (Wikipedie), správce balíčků pro macOS a od verze 2.0.0 také pro Linux, byl vydán ve verzi 4.5.0. Na stránce Homebrew Formulae lze procházet seznamem balíčků. K dispozici jsou také různé statistiky.
Byl vydán Mozilla Firefox 138.0. Přehled novinek v poznámkách k vydání a poznámkách k vydání pro vývojáře. Řešeny jsou rovněž bezpečnostní chyby. Nový Firefox 138 je již k dispozici také na Flathubu a Snapcraftu.
Šestnáctý ročník ne-konference jOpenSpace se koná 3. – 5. října 2025 v Hotelu Antoň v Telči. Pro účast je potřeba vyplnit registrační formulář. Ne-konference neznamená, že se organizátorům nechce připravovat program, ale naopak dává prostor všem pozvaným, aby si program sami složili z toho nejzajímavějšího, čím se v poslední době zabývají nebo co je oslovilo. Obsah, který vytvářejí všichni účastníci, se skládá z desetiminutových
… více »Uplne souhlasim, ze toto je dalsi nevyhoda OOP, o ktere se nemluvi.
Aha ty mluvíš o tomhle:
I kdyz treba v tom C++ se ti to stane pouze pokud pokud ta nova metoda bude abstract (a nebo mozna ne, nechce se mi to domyslet;)), ale v C++ zase pridani metody do tridy zmeni velikost objektu, a ABI ti to stejne rozbije ..
a ne o konfliktu jmen (#4). V tom případě je to ale nevýhoda konkrétní implementace OOP, nikoli OOP jako takového.
Tak jestli to správně chápu, tak všechno, o čem se bavíme je nevýhoda konkrétní implementace OOP, případě většiny implementací podle jakékoli rozumné metriky rozšířenosti.
Tak zatím tu byla řeč o C++ (kde to problém je) a o Javě (kde to problém není). Z toho, co píšeš, mi přijde, že GOBject má blíž k té Javě. Ale stále si nevysvětlil, jak se GObject vyrovná s případem, kdy nejdřív přidáš metodu do potomka a později přibude metoda se stejnou hlavičkou do předka – jak jazyk/kompilátor/vm pozná, která událost nastala dřív a jestli jde o záměrné překrytí nebo shodu jmen?
Tak zatím tu byla řeč o C++ (kde to problém je)Ano.
o Javě (kde to problém není)Není to tak dlouho, co jsi potvrdil, že v Javě problém je, protože nevyžaduje @Override.
Ale stále si nevysvětlil, jak se GObject vyrovná s případem, kdy nejdřív přidáš metodu do potomka a později přibude metoda se stejnou hlavičkou do předka – jak jazyk/kompilátor/vm pozná, která událost nastala dřív a jestli jde o záměrné překrytí nebo shodu jmen?Kam tak spěcháš? Reaguju postupně a nově přidaným komentářem pokrývám všechno včetně toho, že se GObject běžně používá tak, že je z hlediska dědičnosti jen API-stable a ne ABI-stable, i když by i to šlo jednoduše zařídit.
Není to tak dlouho, co jsi potvrdil, že v Javě problém je, protože nevyžaduje @Override.
Ne, to jsou dvě různé věci. V C++ to nebude fungovat, ani když k žádné kolizi jmen nedojde – stačí přidat jakoukoli metodu do předka a celé se to rozbije. Nebo ne?
V Javě je problém jen s tím, když se náhodou trefíš do stejného názvu jaký přibyl v nové verzi předka a dáš metodě jiný význam. Jsou to poměrně okrajové případy.1 V Javě tě na chybějící @Override
upozorní IDE a nástroje na analýzu kódu jako na potenciální chybu. Souhlasím, že by bylo lepší, kdyby @Override
anotaci vynucoval kompilátor (ale to by si zase lidi stěžovali, že jim nová Java nepřeloží staré programy, což je věc, na které si Java dost zakládá).
[1] i s ohledem na to, že se v takových případech často používají spíše rozhraní než třídy, že jsou některé třídy final
, a tudíž je dědit nelze, a že se nové metody do knihovních tříd moc nepřidávají – a zároveň když si do potomka přidáš nějakou svojí metodu, bude se jmenovat třeba setNějakýMůjSpeciálníObjekt()
a jako parametr bude mít NějakáMojeSpeciálníTřída
, takže kolize je vyloučena, protože v knihovní třídě se nic takového neobjeví
Ne, to jsou dvě různé věci. V C++ to nebude fungovat, ani když k žádné kolizi jmen nedojde – stačí přidat jakoukoli metodu do předka a celé se to rozbije. Nebo ne?Jo ty se vyjadřuješ k rozšiřování bez kolize jmen. Nevím, jak přesně to v C++ funguje, ale hádám, že asi dost špatně.
V Javě je problém jen s tím, když se náhodou trefíš do stejného názvu jaký přibyl v nové verzi předka a dáš metodě jiný význam. Jsou to poměrně okrajové případy.Mně to při používání Pythoního multiprocessing API trvalo ani ne pár hodin, než se mi to stalo. Sice se jednalo o kolizi jmen u atributu namísto metody, ale to na věci moc nemění.
Souhlasím, že by bylo lepší, kdyby @Override anotaci vynucoval kompilátor (ale to by si zase lidi stěžovali, že jim nová Java nepřeloží staré programy, což je věc, na které si Java dost zakládá).Javu nedělám, takže nemám problém se bavit jen hypoteticky. Ale tady přece nejde o to, aby něco kompilátor vynucoval, ale hlavně aby volání správně přeložil tak, aby volání z knihovního kódu používalo knihovní verzi a volání aplikačního kódu používalo aplikační verzi, pokud nastává kolize jmen a nejedná se o přepis virtuální metody.
že se nové metody do knihovních tříd moc nepřidávajíNejspíš proto, že je s tím tak velký problém. To ovšem v důsledku blokuje další vývoj, pokud teda nevytvoříš novou odvozenou třídu s nějakým tím číslíčkem.
a zároveň když si do potomka přidáš nějakou svojí metodu, bude se jmenovat třeba setNějakýMůjSpeciálníObjekt() a jako parametr bude mít NějakáMojeSpeciálníTřída, takže kolize je vyloučena, protože v knihovní třídě se nic takového neobjevíNo nevím, já jsem třeba přidal nového člena jménem
name
a nazývat ho mySpecialName
by mi přišlo jednak nepraktické a jednak by mohla nastat kolize, kdybych od té třídy zase dědil. To už můžou rovnou prefixovat názvem třídy a budu na tom líp.
Javu nedělám, takže nemám problém se bavit jen hypoteticky. Ale tady přece nejde o to, aby něco kompilátor vynucoval, ale hlavně aby volání správně přeložil tak, aby volání z knihovního kódu používalo knihovní verzi a volání aplikačního kódu používalo aplikační verzi, pokud nastává kolize jmen a nejedná se o přepis virtuální metody.
Tím bys ale právě rozbil tu kompatibilitu se starými programy – v nové Javě by se chovaly jinak než ve staré.
Představ si, že programátor před deseti lety podědil nějakou třídu a záměrně překryl její metodu, ale nedal tam anotaci @Override
. On chce, aby se všude, kde se pracuje s instancí tohoto potomka, používala jeho metoda.
Dát tam takovouhle implicitní „chytristiku“ by bylo mnohem nebezpečnější (těžko odhalitelné chyby) než vynutit anotaci @Override
(starý kód sice nepůjde přeložit, ale hned víš, kde je chyba a snadno ji opravíš).
No nevím, já jsem třeba přidal nového člena jménem name a nazývat ho mySpecialName by mi přišlo jednak nepraktické a jednak by mohla nastat kolize, kdybych od té třídy zase dědil. To už můžou rovnou prefixovat názvem třídy a budu na tom líp.
Členské proměnné jsou něco jiného. Když dojde ke kolizi jejich jmen, tak ti jednak IDE zobrazí varování, ale hlavně to funguje tak, že předek i potomek pracují každý se svojí proměnnou a navzájem se neovlivňují. Nehrozí tak, že by sis v potomkovi vytvořil proměnnou, později přibyla proměnná se stejným názvem v předkovi a ty jsi z potomka nežádoucím způsobem ovlivňoval chování předka.
Mně to při používání Pythoního multiprocessing API trvalo ani ne pár hodin, než se mi to stalo. Sice se jednalo o kolizi jmen u atributu namísto metody, ale to na věci moc nemění.
Položil bych si následující otázky:
Tím bys ale právě rozbil tu kompatibilitu se starými programy – v nové Javě by se chovaly jinak než ve staré.Už jsem psal, že mě to pro účely hypotetické diskuze absolutně nezajímá.
Členské proměnné jsou něco jiného. Když dojde ke kolizi jejich jmen, tak ti jednak IDE zobrazí varování, ale hlavně to funguje tak, že předek i potomek pracují každý se svojí proměnnou a navzájem se neovlivňují. Nehrozí tak, že by sis v potomkovi vytvořil proměnnou, později přibyla proměnná se stejným názvem v předkovi a ty jsi z potomka nežádoucím způsobem ovlivňoval chování předka.Jak bych na tohle jenom odpověděl slušně, aby mi zase nebyla vyčítána hrubost. Odpovídáš na komentář, kde jsem popsal určitý jev a napsal jsem přesně jakým způsobem a s jakým API se stal. Ty na to reaguješ zcela nepravdivými tvrzeními, ze kterých navíc vyvozuješ, že něco takového nehrozí. Můžeš mi sám, navrhnout, jak bych měl na takovou věc reagovat?
Už jsem psal, že mě to pro účely hypotetické diskuze absolutně nezajímá.
Pak mluvíme o hypotetickém návrhu nějakého nového jazyka na zelené louce. V svých komentářích jsem se snažil brát aspoň trochu ohledy na reálný svět, ve kterém žijeme teď.
kde jsem popsal určitý jev a napsal jsem přesně jakým způsobem a s jakým API se stal
Já jsem psal o tom, jak obdobná situace vypadá v Javě. Myslel jsem, že je to z kontextu zřejmé.
No nevím, já jsem třeba přidal nového člena jménempresne kvuli tomuhle jsem si myslel ze se pouziva viditelnost - private,protected. Neni tomu tak?name
a nazývat homySpecialName
by mi přišlo jednak nepraktické a jednak by mohla nastat kolize, kdybych od té třídy zase dědil. To už můžou rovnou prefixovat názvem třídy a budu na tom líp.
ale v C++ zase pridani metody do tridy zmeni velikost objektu, a ABI ti to stejne rozbije ..Řešení změny velikosti datových struktur asi nejde řešit jinak než dynamickou alokací nebo nějakou hroznou prasárnou.
class MyClass { virtual ~MyClass() {}; virtual void myfunc() = 0; }; class MyHiddenClass : public MyClass { virtual void myfunc() { /* impl */ } }; => class MyClass { virtual ~MyClass() {}; virtual void myfunc() = 0; }; class MyClassV2 : public MyClass { virtual void newfunc() = 0; } class MyHiddenClass : public MyClassV2 { void myfunc() override { /* impl */ } void newfunc() override { /* impl */ } };
class A { public: virtual ~A() {} virtual void a(); private: int a; };Tak generuje toto (x64):
A: offset 0: pointer na první funkci ve vtable -\ offset 8: int a; | | A vtable vypadá takto: | offset 0: typeinfo pointer | offset 8: A::~A <----------------/ offset 16: deleting dtor (volá delete po dtoru) offset 24: A::aPokud přidáte další virtuální funkci, a to na konec, tak se pouze přidá další pointer na konec vtable. Existující kód bude i nadále fungovat a ABI kompatibilita bude zachována, ale jen za předpokladu, že 1) buď nikde nedědím a používám jen tento původní 'interface' přes pointer objektu, co dostanu z knihovny, nebo dědím, ale v mé odvozené třídě už nemám žádné virtuální metody (překrývat existující můžu). Protože jak říkáte, kdybych v odvozené třídě měl další virtuální metody, tak by se vtable překryly a byl by průšvih:
// po změně A v 'knihovně' class A { public: virtual ~A() {} virtual void a(); virtual void b(int); private: int a; }; A: offset 0: pointer na první funkci ve vtable -\ offset 8: int a; | | A vtable vypadá takto: | offset 0: typeinfo pointer | offset 8: A::~A <----------------/ offset 16: deleting dtor (volá delete po dtoru) offset 24: A::a offset 32: A::b // a v aplikaci, co je zkompilovaná ke staré verzi class B : public A { public: void a() override; virtual void c(string); }; B: (zděděno z A) offset 0: pointer na první funkci ve vtable -\ offset 8: int a; | (B nic) | | B vtable vypadá takto: | offset 0: typeinfo pointer | offset 8: B::~B <----------------/ offset 16: deleting dtor (volá delete po dtoru) offset 24: B::a offset 32: B::cJak vidíte, problém, protože když teď předám objekt B do nové verze knihovny (jako A), tak ta teď bude volat v případě A::b špatnou funkci B::c, která je úplně jiná. Takže přidat možná, s rozmyslem a jen tehdy, pokud vím, jak je rozhraní použito, nebo vím, že nemůže být použito jinak (např. zakážu dědit přes private ctor).
No, tam se tvrdí, že přidání metody je binárně kompatibilní, což je.Možná podle nějaké hodně nepraktické definice. Jinak není zjevně kompatibilní ani zdrojově, když může přidání metody vést ke kolizi se jménem, které vzniklo nezávisle za zcela odlišným účelem. Ale to už níže Franta potvrdil.
Může to vést ke změně chováníIrelevantní.
Jinak není zjevně kompatibilní ani zdrojově, když může přidání metody vést ke kolizi se jménem, které vzniklo nezávisle za zcela odlišným účelem.
Můžeš napsat nějaký konstruktivní návrh, jak by sis dědičnost představoval?
Obávám se, že některé problémy dědičnosti jsou z principu neřešitelné. A pohoršovat se nad tím, jak Java (ne)řeší neřešitelné problémy, nedává nejmenší smysl.
Někteří programátoři/architekti z těchto důvodů dědičnost úplně odmítají a jdou pouze cestou kompozice. Já takhle striktní nejsem, dědičnost mám celkem rád a myslím, že má svoje uplatnění, ale je třeba si být vědom těch úskalí. Mj. proto se taky často ve veřejných API používají rozhraní nikoli třídy, případně se používají final
třídy, které nejde podědit (např. String
v Javě).
Můžeš napsat nějaký konstruktivní návrh, jak by sis dědičnost představoval?Můžeš přestat spamovat diskuzi dokola stejným dotazem, ke kterému jsem se pouze musel propracovat? Odpovídám lineárně, na jeden komentář za druhým, máš s tím nějaký problém? :)
Mj. proto se taky často ve veřejných API používají rozhraní nikoli třídy, případně se používají final třídy, které nejde podědit (např. String v Javě).To dává smysl. Jestli jsem se v tom správně zorientoval, tak se na to dá nahlížet buď tak, že dědit třídu knihovního API je vždycky špatně, nebo tak, že se do třídy knihovního API nikdy nesmí přidat nová non-private metoda. Jinak kolize hrozí.
se do třídy knihovního API nikdy nesmí přidat nová non-private metoda. Jinak kolize hrozí.
Omezil bych to na „v rámci stejné minor verze“ (viz #30). Potom souhlas.
dědit třídu knihovního API je vždycky špatně
Vždy ne, stačí používat to sémantické verzování a správně deklarovat závislosti.
A nakonec je i otázka, jestli chceme usilovat o absolutně neprůstřelný systém, kde nemůže dojít k náhodné kolizi ani v 0,00001 % případů a jestli chceme mít možnost bez jakékoli kontroly upgradovat verzi knihovny a nasadit to hned do produkce. IMHO je rozumným kompromisem tohle dělat u patch verzí – tam je to důležité – přijde bezpečnostní oprava, chci upgradovat hned a ne řešit nějakou nekompatibilitu. Ale pokud se zvyšuje minor verze (o major ani nemluvě), tak by se kompatibilita měla otestovat a ne jen slepě věřit, že to bude fungovat – měl by to otestovat jednak někdo v distribuci (resp. chtělo by to automatické testy) a jednak ten, kdo ten systém provozuje – u kritických systémů to nasadí nejdřív na testovací prostředí a tam to nechá nějakou dobu běžet.
Omezil bych to na „v rámci stejné minor verze“ (viz #30). Potom souhlas.Kontext je zřejmý, v rámci kompatibilní verze, jinak to jenom komplikuješ, v mnohých projektech jsou mezi sebou kompatibilní i verze, které se nazývají minor, nemusíme hned specifikovat verzovací schéma, víme přece, o čem se bavíme.
Vždy ne, stačí používat to sémantické verzování a správně deklarovat závislosti.To je ta druhá možnost, nikdy nerozšiřovat původní třídu a namísto toho vytvořit její novou verzi, ale to mi zatím subjektivně přijde docela divoké.
Jen pro pořádek: tohle #30 a #21 jsou dvě úplně jiné věci (jednou verzování modulů, podruhé verzování jednotlivých tříd).
A change to a type is binary compatible with (equivalently, does not break binary compatibility with) pre-existing binaries if pre-existing binaries that previously linked without error will continue to link without error.Jak moc je či není praktická... Jinak změna klidně může být zdrojově nekompatibilní a přitom být binárně kompatibilní, takže tohle by stálo za to rozlišovat.
A change to a type is binary compatible with (equivalently, does not break binary compatibility with) pre-existing binaries if pre-existing binaries that previously linked without error will continue to link without error.Tak linkovat to bude, ale jde o to, která metoda se bude z knihovny volat.
Jinak změna klidně může být zdrojově nekompatibilní a přitom být binárně kompatibilní, takže tohle by stálo za to rozlišovat.Rozhodně.
Tak linkovat to bude, ale jde o to, která metoda se bude z knihovny volat.Když jsou všechny metody virtuální, tak samozřejmě ta z potomka, že. C# je v tomhle trochu zajímavější: tam nejen že metody nejsou ve výchozím stavu virtuální (musí se přidat klíčové slovo virtual), ale potomek může metodu z předka namísto překrytí (jen u virtuálních metod, klíčové slovo override) i zastínit (mělo by se použít klíčové slovo new, jinak to vede na warning). Bohužel jak tam funguje binární kompatibilita netuším.
Když jsou všechny metody virtuální, tak samozřejmě ta z potomka, že.Což je v případě shody jmen chyba.
C# je v tomhle trochu zajímavější: tam nejen že metody nejsou ve výchozím stavu virtuální (musí se přidat klíčové slovo virtual), ale potomek může metodu z předka namísto překrytí (jen u virtuálních metod, klíčové slovo override) i zastínit (mělo by se použít klíčové slovo new, jinak to vede na warning). Bohužel jak tam funguje binární kompatibilita netuším.A může virtuální metodu zastínit nová virtuální metoda? Nedělám v C#, takže se omlouvám, jestli je to hloupý dotaz, jestli jsou například všechny metody virtuální. Tohle by mohlo zajímat Frantu, který zde mluvil o @Override, leccos by se vyřešilo, kdyby jazyky zavedly doporučení uvádět u všech virtuálních metod uvádět buď @New nebo @override. Radši rovnou upozorním, že opět mluvím především o konceptu, nikoliv o konkrétní syntaxi. Nové kompilátory by mohly varovat, pokud se u virtuální metody explicitně neurčí překrytí/zastínění. Jinak slyšel jsem od pár lidí, že se C# odkolnil od Javy dobrým směrem a klíčové slovo new tomu zrovna nasvědčuje.
leccos by se vyřešilo, kdyby jazyky zavedly doporučení uvádět u všech virtuálních metod uvádět buď @New nebo @override.Nektere pripady do resi, ale rozhodne ne vsechny. Napr. situaci, kdy program pouziva tridu B z knihovny libB, ktera je potomkem tridy A z knihovny libA. Trida A zavede nejakou metodu s @new, program pro na sve objekty zacne volat tuto metodu a nasledne nova verze knihovny B zavede metodu stejneho jmena take s @new. Program pak bude volat metodu z libB, prestoze zamerem bylo volat metodu z libA. A to bez jedineho warningu.
Java (případně okořeněná OSGi).
Ale co se týče dědičnosti a kolize s názvu metody s poděděnou třídou – to je víceméně neřešitelný problém z principu – kompilátor/VM nepozná, jestli jsi metodu překryl záměrně nebo jestli jsi ji měl napsanou dříve a teď jsi jen upgradoval knihovnu předka, ve které je stejně pojmenovaná metoda.
Leda vyžadovat anotaci @Override
(dnes je jen doporučená a ochrání tě jen před opačným problémem – když ji tam dáš a v předkovi taková metoda nebude, považuje se to za chybu).
Leda vyžadovat anotaci @Override
napr. Scala ji vyzaduje
Ale co se týče dědičnosti a kolize s názvu metody s poděděnou třídou – to je víceméně neřešitelný problém z principu – kompilátor/VM nepozná, jestli jsi metodu překryl záměrně nebo jestli jsi ji měl napsanou dříve a teď jsi jen upgradoval knihovnu předka, ve které je stejně pojmenovaná metoda.Mně to napadlo právě proto, že v C ten problém nenastává. Ani při rozumném použití GObject.
Leda vyžadovat anotaci @OverrideTo by znělo jako řešení, jen přemýšlím, jen popravdě nedomýšlím, jak by tohle fungovalo v ABI u kompilovaného jazyka.
Mně to napadlo právě proto, že v C ten problém nenastává.
Když nemá dědičnost, tak asi ne
Ani při rozumném použití GObject.
Jak to tam funguje? Co když do předka předám stejnou metodu, jako někdo přidal do potomka? Dojde k nechtěnému překrytí metody? Nebo to nejde?
Jak GObject pozná, jestli šlo o záměr programátora nebo neúmyslnou shodu?
struct
, který mimo jiné obsahuje i objekt pro nadřazenou třídu. Tak to jde až po třídu nejvyšší úrovně. Takže zatímco nová virtuální metoda pointer na funkci dostupný přes my_class.my_method
, stejně pojmenovaná zděděná virtuální metoda je dostupná přes my_class.parent.my_method
, případně pomocí přetypování. Každopádně je pointer na funkci uložený na jiné relatiní adrese k začátku struktury.
V implementaci, která hledá funkce podle jména, bys téhož výsledku dosáhl prefixováním každého jména názvem třídy. Tady je to ale přecijen řešeno o něco elegantněji.
Ale z hlediska API to mají podchycené. Neexistuje žádný sdílený namespace, kde by k tomu mohlo dojít. Takže pojem „stejná metoda“ ztrácí význam a tudíž se ti něco takového nemůže stát.
Takže neexistuje nic jako překrýt metodu předka v potomkovi? Pak ten problém nastat nemůže, ale nejde pak ani o dědičnost, o které se tu bavíme – např. v Javě můžeš překrýt v potomkovi metodu předka, aby se chovala jinak, a předat instanci potomka kódu, který umí pracovat s předkem – a tento kód zavolá tvoji překrytou metodu s jiným chováním (např. jsi tam přidal logování nebo nějaké vylepšení), aniž by o tom musel vědět (myslí si, že pracuje s předkem, resp. neřeší to).
V implementaci, která hledá funkce podle jména, bys téhož výsledku dosáhl prefixováním každého jména názvem třídy. Tady je to ale přecijen řešeno o něco elegantněji.
Pak by ale nešlo překrývat metody předka v potomkovi, protože volající kód by musel vědět, kterou metodu chce volat (jestli předka nebo potomka).
Takže neexistuje nic jako překrýt metodu předka v potomkovi?Napíšeš novou funkci a uložíš ji do příslušného pointeru. Různé odvozené třídy pak mají na dané pozici různé funkce.
Pak by ale nešlo překrývat metody předka v potomkovi, protože volající kód by musel vědět, kterou metodu chce volat (jestli předka nebo potomka).Při překrývání musíš samozřejmě prefix zachovat.
Vzhledem k tou že ani C nemá standardizované ABI, je celý blog úplně mimo mísu.Vzhledem k tomu, že je řeč o ABI knihoven, mám obavu, že jsou mimo mísu jen některé komentáře.
Udržovat knihovnu v jakémkoliv jazyce, tak aby s tím byli její uživatelé spokojení, je zatraceně těžká věc.To je všechno hezké, ale zkusím diskuzi trochu vrátit do kontextu. V binárních linuxových distribucích je relativně běžné upgradovat komponentu, na které je závislá jiná komponenta. A v případě céčkovských knihoven není problém dodávat funkcionalitu aniž by bylo potřeba upravovat nebo dokonce jenom rebuildovat tu závislou komponentu. Co se tak pohybuju mezi céčkovými linuxovými vývojáři, tak to zpravidla berou jako samozřejmost a přitom to skoro vypadá, že touto možností z jazyků běžně v linuxových distrech používaných disponuje jenom C.
Ne, to fakt normální není Je to na úrovni crackování binárky, přepisování instrukcí kdesi uvnitř nebo pirátských překladů (opět úprava binárky, kde přeložený výraz musel být stejně dlouhý jako ten původní).
Pro inspiraci, jak se řeší modularita a rozšiřování funkcí programu, se podívej na Netbeans, Eclipse, jEdit, OSGi, META-INF/services…
Nové JARy/třídy se přidávají na CLASS_PATH
nebo se načítají za běhu programu – nebudeš je přibalovat do původního JARu, natož abys v něm něco přepisoval.
Pokud ti hodně záleží na kompatibilitě a zároveň chceš umožnit dědění tříd, které tvoří veřejné API, tak můžeš použít verzování tříd.
Znamená to, že třídu, kterou jednou vydáš jako součást veřejného API nesmíš už nikdy měnit – resp. nesmíš měnit hlavičky jejích metod nebo zásadním významem jejich vnitřek (např. metoda bude vracet pořád boolean
a na vstupu bude mít pořád int
a String
, ale v nové verzi bude vracet negaci a význam parametrů bude jiný – to je samozřejmě špatně, ale to bys neměl dělat bez ohledu na dědičnost).
Pokud ji chceš rozšířit, vytvoříš třídu Třída2
, Třída3
, Třída4
…, které se postupně dědí. Náročné je to v tom, že v kódu, který má používat novou funkcionalitu musíš mít výhybky, ověřovat instanceof TřídaX
a přetypovávat + se nějak vyrovnat s případem, kdy nová funkcionalita chybí (dostal jsi jen instanci předka).
Ten, kdo chce podědit třídu z veřejného API, podědí konkrétní verzi třídy (do které mu už nikdo nic dalšího nepřidá, takže ke kolizi nemůže dojít). Ale na druhou stranu nemůžeš automaticky profitovat z nových funkcí – musíš upravit svůj kód potomka a přepsat tam třeba Třída2
→ Třída3
a vydat novou verzi potomka.
Ale co když člověk používá dědičnost? Tam přece jakékoli přidání funkce vede na potenciální kolizi s odvozenou třídou.IMHO tohle je problem, ktery nesouvisi ani tak s OOP jako spis s namespace managementem daneho jazyka. C nema namespaces, ktere by staly za zminku, proto v nem vsichni pouzivaji explicitni prefixy. Jmeno s prefixem tedy defacto slouzi jako absolutni identifikator a kolize jmen z ruznych namespaces tedy nenastavaji. Pokud ale mas jazyk s namespaces a pouzijes nejake (relativni) jmeno v kontextu, kde je mozne ho resolvovat ve dvou ruznych namespaces s tim, ze se nachazi jenom v jednom, tak mas do boudoucna problem. Jak to souvisi s OOP? Tridy je mozne chapat jako (vnorene) namespaces. Pouziti metody objektu tedy znamena, ze jmeno jmeno je treba resolvovat v hierarchii namespaces prislusnych trid. Pokud by se vzdy pouzivala absolutni jmena (a la C), tak ke kolizi nedojde. Napr. mame tridu xxx a jejiho potomka yyy. Pokud do yyy pridam metodu yyy_print(). tak nemuze dojit ke kolizi s pripadnym pozdejsim pridanim metody do xxx. Pokud bych ale v yyy chtel cilene prekryt metodu tridy xxx, tak pro ni pouziju jmeno xxx_print(). Ale je pravda, ze vzdy pouzivat absolutni jmena je dost otravne. Tohle je problem, nad kterym jsem uz kdysi premyslel, a napadlo me akorat: Pred kompilaci programu vlozit fazi, ktera provede strojove resolvovani jmen (tedy pro kazdy kontext a relativni jmeno se nalezne absolutni jmeno) a tuto informaci ulozi explicitne vedle zdrojaku. Pripadne muze hlasit kolize, ktere musi vyvojar vyresit. Pri jakekoliv pozdejsi kompilaci (napr. u uzivatele) uz jsou tedy absolutni jmena znama a ke kolizim dojit nemuze. Vysledek teto faze je mozne znovupouzit i pri pozdejsim behu teto faze (napr. po uprave zdrojaku) a automaticky tak vyresit kolize, ktere se od minula objevily (napr. proto, ze nove verze knihoven exportuji nove symboly). Alternativou by bylo by pri importu namespaces z knihoven vzdy explicitne uvadet pozadovanou verzi s tim, ze knihovna by u kazdeho jmena mela informaci, v ktere verzi se objevilo. To je svym zpusobem vyrazne jednodussi nez predchozi pristup, ale nefungoval by v situaci, kdy by knihovna mela nelinearni vyvoj (napr. v dusledku forku).
A jak do toho zapadá ten nápad s vyžadováním @Overrides?Nic jako @Overrides by nebylo treba. Pokud by autor potomka chtel cilene pretizit virtualni metodu, musel by se v definici explicitne odkazat ve jmene na predka, ktery danou virtualni metodu zavedl.
To by znamenalo, že se metoda odvozené třídy použije jenom v kódu psaném pro odvozenou třídu a nikoli v kódu psaném pro upravenou původní třídu.Nova metoda se pouzuje jen takto. Pretizena virtualni metoda se pouzije i vsude v kode pro puvodni tridu.
V zásadě by to znamenalo, že by člověk klidně mohl mít v hierarchii dvě virtuální metody stejného jménaAno. To je proste rozdil mezi slovy a koncepty. Pokud definuji nesouvisejici metodu/funkci/tridu a dam ji stejne jmeno, ktere koliduje s jiz existujici, tak to je analogicke k situaci homonym v prirozenem jazyce. Uzivatel akorat musi vedet, o co jde, a mit moznost explicitne vybrat, na ktery koncept odkazuje.
jen od třídy, která by metodu znovu definovala, dále by se používala ta nová.Samozrejme i tam by bylo mozne pouzit tu puvodni tim, ze se pri odkazu pouzije identifikace prislusneho namespace.
Nic jako @Overrides by nebylo treba. Pokud by autor potomka chtel cilene pretizit virtualni metodu, musel by se v definici explicitne odkazat ve jmene na predka, ktery danou virtualni metodu zavedl.Spíše mě zajímo @Overrides bez explicitního odkazování.
Uzivatel akorat musi vedet, o co jde, a mit moznost explicitne vybrat, na ktery koncept odkazuje.To by v tomto případě typicky nebyl problém. Pokud by se jednalo jen o aplikaci a knihovnu, tak je zjevné, že aplikace metodu nadřazené třídy nezná. Ve chvíli, kdy je potřeba, aby ji znala, stačí kolidující metodu v aplikaci přejmenovat. Jiná věc je dědičnost od knihovny ke knihovně a od té k aplikaci. Tam by samozřejmě mohlo mít smysl z aplikace specifikovat, že mě zajímá metoda nepřímo používané knihovny namísto přirozeně dostupné metody přímo používané knihovny.
Samozrejme i tam by bylo mozne pouzit tu puvodni tim, ze se pri odkazu pouzije identifikace prislusneho namespace.Jasně. V tom výjimečném případě, kdy je to potřeba.
@Override
nikoli @Overrides
Jiná věc je dědičnost od knihovny ke knihovně a od té k aplikaci. Tam by samozřejmě mohlo mít smysl z aplikace specifikovat, že mě zajímá metoda nepřímo používané knihovny namísto přirozeně dostupné metody přímo používané knihovny.
Tohle už je takový šamanismus a alchymie – potenciálně dost nebezpečné a nevyzpytatelné. Když někdo překryl určité metody a dal obecně třídě trochu jiné chování, měl k tomu nějaký důvod, a takhle poděděné by to mělo dávat nějaký smysl a být konzistentní. Když ale zvenku ten jeho kód obejdeš a pokoutně zavoláš metodu předka, tak to může vést na dost podivné chování, nekonzistentní a nevyzpytatelné výsledky.
Pokud je to zvenku1, tak ano. Autor potomka měl nějakou koncepci, nějaké důvody, proč překryl určité metody… a takhle dohromady ta třída potomka dává smysl, je konzistentní. Ale kdybys zvenku ten jeho kód přeskočil a chtěl volat přímo kód předka, tak se to může chovat chybně, nevyzpytatelně. To raději použij kompozici než dědičnost.
[1] z potomka tu možnost samozřejmě máš, slouží k tomu klíčové slovo super
: @Override public String getX() { return super.getX(); }
Tohle už je takový šamanismus a alchymie – potenciálně dost nebezpečné a nevyzpytatelné. Když někdo překryl určité metody a dal obecně třídě trochu jiné chování, měl k tomu nějaký důvod, a takhle poděděné by to mělo dávat nějaký smysl a být konzistentní.Myslim, ze nerozlisujes dva pripady, ktere diskutujeme - pretizeni virtualni metody vs. zalozeni nove virtualni metody se stejnym jmenem. Zatimco u toho prvniho by se vzdy mela volat pretizena metoda, zatimco u toho druheho pripadu se jedna o rovnocenne moznosti (a stejne jmeno maji dost mozna jen nahodne) a volat puvodni virtualni metodu je zcela korektni. Pokud by autor potomka to tak nechtel, tak by explicitne pretizil metodu predka misto vytvareni nove.
Pokud by autor potomka to tak nechtel, tak by explicitne pretizil metodu predka misto vytvareni nove.Přesně tak. Navíc toto autor nedělá vědomě. Může buď přehlédnout, že metoda toho jména existuje, nebo hůře, může se metoda na předkovi objevit dodatečně při aktualizaci knihovny.
Pokud se nejedná o překrytí, ale o dvě různé metody1, tak ano – volání metody původního předka je samozřejmě legitimní, nic proti tomu.
(jen pro zajímavost: funguje to takhle někde?)
[1] které se vlastně ani nejmenují stejně, protože každá je v nějakém jiném „jmenném prostoru“
(jen pro zajímavost: funguje to takhle někde?)Znam takove objektove modely ze LISPu/Scheme. Tam jsou namespace zcela oddeleny od trid. Jmena odkazuji na 'genericke funkce' a jednotlive tridy pridavaji sve 'virtualni metody' do dane 'generickych funkci'. Pokud chces pretizit metodu, tak pridas metodu do existujici genericke funkce, zatimco pokud definujes uplne novou, tak zridis i novou generickou funkci. Namespace se postara o (lexikalni) vyber genericke funkce, Genericka funkce sama se postara o (dynamicky) typovy dispatch.
To by v tomto případě typicky nebyl problém. Pokud by se jednalo jen o aplikaci a knihovnu, tak je zjevné, že aplikace metodu nadřazené třídy nezná. Ve chvíli, kdy je potřeba, aby ji znala, stačí kolidující metodu v aplikaci přejmenovat.Ano, z hlediska citelnosti a prehlednosti je urcite lepsi se kolidujicim jmenum vyhnout. Z hlediska korektnosti a zpetne/dopredne kompatibility je dobre ten jazyk navrhnout tak, ze by i pri kolizich dobre fungoval. Akorat by to pri necekanych kolizich mohlo napr. vypisovat warningy pri kompilaci.
Jiná věc je dědičnost od knihovny ke knihovně a od té k aplikaci. Tam by samozřejmě mohlo mít smysl z aplikace specifikovat, že mě zajímá metoda nepřímo používané knihovny namísto přirozeně dostupné metody přímo používané knihovny.Tady bych zminil, ze ten postup zminovany v mem prvnim postu (predposledni odstavec) resi jak pripad, kdy danou metodu nejdrive pridala knihovna potomka a az v budoucnosti knihovna predka, tak pripad opacny. V obou pripadech by se (na zaklade ulozenych vysledku o resolvovani) pri pozdejsi kompilaci pouzilo 'spravna' metoda (ta, ktera existovala v dobe puvodniho resolvovani jmen).
Tady bych zminil, ze ten postup zminovany v mem prvnim postu (predposledni odstavec) resi jak pripad, kdy danou metodu nejdrive pridala knihovna potomka a az v budoucnosti knihovna predka, tak pripad opacny. V obou pripadech by se (na zaklade ulozenych vysledku o resolvovani) pri pozdejsi kompilaci pouzilo 'spravna' metoda (ta, ktera existovala v dobe puvodniho resolvovani jmen).Už rozumím. Ale osobně generovaná data nepovažuju za součást zdrojových kódů. Takže pokud bych udržoval jen skutečné zdrojové kódy, tak bys zřejmě docílil ABI kompatibility, ale API kompatibilita by byla omezena nutností dospecifikovat třídu u volání kolidujícího jména.
Nic jako @Override
by nebylo treba. Pokud by autor potomka chtel cilene pretizit virtualni metodu, musel by se v definici explicitne odkazat ve jmene na predka, ktery danou virtualni metodu zavedl.
Co když tam bude posloupnost dědění A ← B ← C
? Původně bude metoda jen v A
a já ji podědím v C
. V další verzi ale podědí tuhle metodu i B
a já budu muset přepisovat C
.
Každý z těch přístupů má svoje, chápu, že ten tvůj je teoreticky bezpečnější, ale přijde mi možná až moc paranoidní. Skutečně je potřeba rozlišovat, jestli překrývám metodu přímého předka nebo nepřímého?
Osobně bych dal přednost @Override
anotaci vynucené kompilátorem, kde stačí říct, že překrývám metodu předka, ale už nemusím říkat, kterého.
(kartami zamíchá až vícenásobná dědičnost – chceme-li ji podporovat – tam už bych považoval za nutné deklarovat, kterého předka metodu překrývám)
Co když tam bude posloupnost dědění A ← B ← C? Původně bude metoda jen v A a já ji podědím v C. V další verzi ale podědí tuhle metodu i B a já budu muset přepisovat C.Pokud je metoda v A a jiné třídy ji pouze podědí, pak přece žádná kolize nenastává. Bavíme se tady o případu, kdy se metody shodují v názvu a přitom jedna není specializací druhé.
Co když tam bude posloupnost dědění A ← B ← C? Původně bude metoda jen v A a já ji podědím v C. V další verzi ale podědí tuhle metodu i B a já budu muset přepisovat C.Pokud C pretizi metodu definovanou z A, tak se bude odkazovat na A. To, zda ji pretizi ci nepretizi i B, na veci nic nemeni.
To, zda ji pretizi ci nepretizi i B, na veci nic nemeni.
A co by potom dělal následující kód?
public void něcoDělej(B b) { b.první(); b.druhá(); }
Tento kód někdo zavolá a jako parametr předá instanci třídy C
.
Metoda první()
je jen v A
, tam je to jednoduché.
Ale metoda druhá()
je v A
a C
. Jak se to bude chovat?
A jak se to bude chovat po úpravě kódu třídy B
, kdy metoda druhá()
bude v A
, B
i C
, ale C
se nezmění, bude stále deklarovat, že překrývá metodu A
? (něcoDělej()
deklaruje stále parametr typu B
, ale reálně dostává instanci C
, to se nemění)
Obávám se, že spolehlivé a bezpečné řešení by vyžadovalo jiný způsob zápisu volání metod, který by byl mnohem méně přehledný.
Ale metoda druhá() je v A a C. Jak se to bude chovat?Pokud je metoda druhá() definovana v C jako pretizeni metody z A, tak se zavola druhá() z C (predpokladame-li dynamickou vazbu). Pokud je metoda druhá() definovana v C nezavisle, tak se zavolá druhá() z A.
A jak se to bude chovat po úpravě kódu třídy B, kdy metoda druhá() bude v A, B i C, ale C se nezmění, bude stále deklarovat, že překrývá metodu A?Tak se stale zavolá druhá() z C. Tedy v pripade, ze by metoda druhá() v B byla take definovana jako pretizeni te z A. Zajimave by to bylo v situaci, kdy by B namisto toho definovala metodu druhá() nezavisle. Pak by preklad po uprave kodu zavisel na ulozenych informacich o resolvovanych symbolech. Pokud by něcoDělej() vznikla pred tim, nez se pridala druhá() do B (a tedy by bylo poznamenano ze druhá() v tomto kontextu se resolvuje na virtualni metodu z A nebo jeji pretizeni). Pak by se nejden zavolala druhá() z C pro instanci tridy C, ale dokonce by se zavolala druhá() z A pro instanci tridy B. Pokud by něcoDělej() vznikla pozdeji (nebo by programator explicitne smazal ulozene vysledky resolvovani), tak by se to chapalo jako volani druhá() z B jak pro instanci tridy B, tak pro instanci tridy C. V obou pripadech z tohoto odstavce by to vypisovalo warning o kolizi metody druhá() mezi A a B.
Pavlix: Spíše mě zajímo @Overrides bez explicitního odkazování.
xkucf03: kde stačí říct, že překrývám metodu předka, ale už nemusím říkat, kteréhoOno v tom mem pristupu vlastne neni nutne explicitne odkazovat na predka. Staci rozlisit, zda se zaklada nova metoda, nebo pretezuje predchozi. Resolvovani pro nalezeni absolutniho identifikatoru dane metody (v pripade 'pretizeni') se zvladne v ramci kompilace kodu knihovny potomka.
Jinak řešením toho problému s kolizí jmen a nechtěným překrytím metody, která později přibyla v předkovi, je používat sémantické verzování a nějaký modulární framework např. OSGi.
Ve svém kódu, který dědí, deklaruješ závislost na knihovně předka např. 1.1.x a OSGi zajistí, že se ti načte správná verze. Při změně patch verze se opravují chyby, ale nesmí přibývat nová funkcionalita – na úrovni tříd (tvořících veřejné API) tedy nemůže přibýt nová metoda. Pokud by metoda přibyla, musela by se zvýšit minor verze, tzn. byla by to knihovna 1.2.0 a s tou jsi nedeklaroval kompatibilitu, takže se nepoužije.
Jestliže nepoužíváš dědičnost, tak můžeš deklarovat kompatibilitu jako větší nebo rovno 1.1.0 a menší než 2.0.0 a bude ti to fungovat, protože dodatečné metody a funkcionalita ti nevadí – stačí, že to umí vše, co uměla verze 1.1.0, což platí.
OSGi je jen příklad – stačí se podívat do /usr/share/java
– jsou tam různé verze různých knihoven instalované vedle sebe – program by si pak měl vybrat tu správnou, se kterou je kompatibilní.
Stejný princip jde aplikovat i na jiné jazyky (pokud s tím není vyloženě technický problém jako u toho C++). Některé distribuce jako GuixSD tomu přímo nahrávají (umožňují instalovat více verzí stejného balíku).
class MojeTrida { static MojeTrida* debilni_factory(); virtual ~MojeTrida(); void delej_neco(); protected: MojeTrida(); }; struct MojeTajnaImplementace : public MojeTrida { int sracka1, sracka2; }; void MojeTrida::delej_neco() { static_cast<MojeTajnaImplementace*>(this)->sracka1 = 20; }Ve výsledku, všechny řešení jsou sračky a zůstaneme u C. Já jsem nějak C++ přestal mít rád. Nutí mě do veřejného API očkovat tuny hlavičkových souborů, co tam musí být jen kvůli interní implementaci, to pak prodlužuje nechutně kompilaci. Radši si těch pár písmenek na víc napíšu.
interface A { void a(); } interface B { void a(); } class C : A, B { void A.a() {} void B.b() {} }V C# teda neprogramuji, ale tohle se mi na něm líbí, co by bylo hezké i v jiných jazycích, třeb java, C++ a D. Velmi se to hodí, když narazíte na nutnosti. V Javě 8 přistálo něco jako default metody v interfacu. To je taky vskutku nebezpečná věc, protože přidávají nové metody do interfacu s nějakou výchozí implementací, a pokud metodu s takovým jménem zrovna máte ve vaší třídě, je problém. Jinak proč se mi poslední doubou nelíbí C++, od C++11 se do standardní knihovny dostává příliš 'bloatu'. Za vlákna jsem třeba rád, ale za nedomyšlené async() a nedomyšlený std::future, nebo zbytečně přeplácané a složité std::chrono. A hlavičkové soubory toho tahají čím dál víc jako svoje 'závislosti' a lezou vám do vaší veřejné implementace. A nedej Bože, když to zkombinujete s boostem, to je pak utrpení, hlavně když v současné době C++11/14 obsahuje duplicitu půlku z toho.
V Javě 8 přistálo něco jako default metody v interfacu. To je taky vskutku nebezpečná věc, protože přidávají nové metody do interfacu s nějakou výchozí implementací, a pokud metodu s takovým jménem zrovna máte ve vaší třídě, je problém.
Ono to taky není určené k programování stylem: „mám nějakou třídu, která dělá kde co, a teď k ní přidám ještě tohle rozhraní… a budu se divit“ – smysluplné a zamýšlené využití je v tom, že vytvoříš jednoúčelovou třídu (typicky jako lambda výraz) implementující právě to jedno rozhraní. Výchozí metoda definovaná v rozhraní pak slouží k tomu, že nějak obaluje/vylepšuje funkcionalitu, kterou poskytuješ v tom lambda výrazu – výsledkem je elegantní a úsporný kód.
V Javě 8 přistálo něco jako default metody v interfacu. To je taky vskutku nebezpečná věc, protože přidávají nové metody do interfacu s nějakou výchozí implementací, a pokud metodu s takovým jménem zrovna máte ve vaší třídě, je problém.Metoda zděděná ze třídy má vždycky přednost před metodou zděděnou z rozhraní. Takže problém je spíš opačný -- když počítám s tím, že nějakou metodu zdědím z rozhraní, ale zároveň dědím od nějaké třídy a ta v nové verzi přidala metodu stejné signatury jako má to rozhraní.
Proto se ty metody v rozhraní jmenují default
– je to jen výchozí implementace, kterou můžeš ve třídě překrýt.
Pokud máš dobrý návrh, tenhle problém nenastane. Pokud máš špatný návrh, stanou se ti mnohem horší věci.
Dědičnost je poměrně „intimní“ vztah a nehodí se na všechno. Dědit od cizích tříd, které ti může kdykoli někdo pod rukama změnit, není zrovna optimální.1 Splácat víc věcí do jedné třídy taky není dobrá praktika.
Co má být vlastně cílem? Nějaký god object, který jednou použiješ v roli jeho předka a jindy v roli instance toho rozhraní? Každá půlka toho objektu dělá zjevně něco jiného2 a není důvod nacpat obě do jedné třídy. Vše nasvědčuje tomu, že se měla použít spíš kompozice než dědičnost.
[1] možná řešení už tu padla: používat ve veřejném API spíš rozhraní než třídy, používat final
třídy, neměnit nečekaně třídy, které někdo dědí, verzovat třídy, používat sémantické verzování modulů, testovat (měl bys mít jednak jednotkové testy k té třídě a jednak integrační/systémové testy celé aplikace)…
[2] jinak by autor třídy předka počítal s tím, že se používá v souvislosti s tím rozhraním a nepřidával by do ní stejnou metodu, nebo by ji tam naopak přidal záměrně a mělo by to smysl
a jednak integrační/systémové testy celé aplikace)…Testování na systému, který umí aktualizovat jednotlivé komponenty, nemusí pomoct. Sice se to obecně řešní doporučením, že systém musí být plně aktualizovaný (což velcí hráči dělají), ale nejsem si jistý, zda to tak v praxi funguje.
[2] jinak by autor třídy předka počítal s tím, že se používá v souvislosti s tím rozhraním a nepřidával by do ní stejnou metoduA rozhraní se rozšiřovat nedají? Pokud ne, viděl bych to jako zásadní omezení, protože pak ze jenom celý problém přenáší na rozhraní, která se rovněž budou muset verzovat.
Pokud vím, tak Python nemá přetěžování metod jako C++ (stejný název metody, různé parametry), takže při přidání metody, která ještě neexistuje (ať už v aktuálním objektu nebo předkovi) nehrozí kolize.O přetěžování (overloading) ovšem vůbec nebyla řeč.
Stejnětak rozšiřitelné datové struktury jdou implementovat triviálně. K zachování API i ABI kompatibility stačí objekty vždy alokovat uvnitř knihovny dynamicky a volajícímu je zpřístupňovat výhradně pomocí funkcí. Uživatel tak vlastně dostává jen identifikátor, který knihovna interně používá jako pointer na objekt v paměti.
Řešit všechno přes pointery a přístupové funkce (které navíc ani nebudou smět být inline) znamená horší výkon, což někomu vadit nemusí, ale pro mnohé projekty to může být naprosto nepřijatelné. A třeba důsledně nahrazovat všechny vnořené struktury pointery znamená i zvýšené riziko chyb. Takže ano, určitě by se to takhle řešit dalo, ale ani zdaleka to není univerzálně použitelný recept.
Jak už je v poslední době nedobrým zvykem, opět nechápu, o co ti vlastně jde.Popravdě si nepamatuju dobu, kdy by tomu bylo jinak. Diskuze je otevřená, přispívej dle svého uvážení a smiř se s tím, že dělám totéž.
Taky nektery lidi zastavaj nazor ze dedicnost by se nemela pouzivat vubec.Jenže v Javě bez dědičnosti neuděláte podtyp třídy - je to svázané.
No prave ze existuje nazor ze podtypy trid by se vubec nemely delatTo je ale obecně něco jiného než dědičnost.
Dalsi moznosti jak to "resit", je postavit celou situaci tak aby se to vubec resit nemuselo.Tak já se bavil v kontextu komponentového systému jako je třeba Fedora, kde se komponenty běžně samostatně aktualizují.
V C++ existuji header-only knihovny,To je po binární stránce ekvivalent bundlingu a tudíž z tohoto pohledu naprosto zbytečná věc. Stejně dobře můžeš dynamické knihovny bundlovat nebo buildovat staticky.
Nase aplikace je certifikovana v JKD 1.3, JAXB 1.0.2, JGL 3.0" a tim je to poreseny.Tak pokud ke všemu vychází bezpečnostní aktualizace oddělené od funkčních updatů, tak bez problémů.
O dlouhodobou udrzbu SW nema nikdo zajem.Tak já to mám jako primární task v práci. Takže jestli o to nikdo nemá zájem, tak by mě měli každou chvíli vyhodit. :)
Tak já se bavil v kontextu komponentového systému jako je třeba Fedora, kde se komponenty běžně samostatně aktualizují.V kontextu Linuxu existuje jeste moznost verzovat symboly na urovni .ELF formatu. To ale prakticky nikdo nepouziva, coz je skoda. Kdyby to bylo privetivejsi a kdyby z kompilatoru padaly nejaky metadata, ze kterych by bylo mozne vycist (alespon nektere) zmeny v ABI tak by to urcite pomohlo. Vyvoj se ale ubira jinym smerem.
V kontextu Linuxu existuje jeste moznost verzovat symboly na urovni .ELF formatu.O nějakém verzování pořád mluví lidi od glibc. Možná bych se na to měl někdy podívat. Ale otázku to nijak nemění. Verzování v ELF bude nejspíš funkčně dost podobné verzování v názvu, což u funkcí není zase až tak složité. V obou případech člověk tak jako tak musí udržovat starou i novou verzi.
$ readelf --symbols /usr/lib64/lib* 2> /dev/null | awk -F '@@' '{ print $2 }' | sed 's/_.*//' | sort -u | wc -l 194(jsem linej ten prikaz nejak ucesat, zakladni predstavu to myslim dava)
Tiskni
Sdílej: