Zrovna tohle je v C++ špatně navržené. ... Ale považuji za chybu, že privátní metody a proměnné jsou vidět v hlavičkovém souboru a co hůř, jejich změna rozbije binární kompatibilitu.
To jako fakt?! Hm, dobrá sračka...
Ono to má technický důvod. Ty struktury mají fixní velikost, takže se rozjedou pointer offsety pro přístup k atributům. Pokud to ABI musí být stabilní, je potřeba to rozdělit na stabilní "proxy" interface a privátní data s negarantovanou strukturou.
Jenže jak to udělat, abych mohl rozhraní rozšiřovat a nerozbil při tom binární kompatibilitu? I když mám čistě abstraktní/virtuální třídu, tak do ní nemohu jen tak přidat metodu.
Do třídy se dají na konec přidávat další metody bez rozbití kompatibility. Nevím, jestli je to teoreticky OK, ale prakticky to funguje. Samozřejmě, že to rozhodí metody přidané v odvozených třídách.
Tohle jsem si zkoušel -- při přidání metody mezi ostatní se to rozbije (původní kód volá jinou metodu -- a pokud mají stejnou signaturu, tak to nespadne, ale dělá to něco jiného, než by mělo), zatímco když novou metodu přidám nakonec, tak se nic nerozbije. Ale pak jsem četl, že toto chování není zaručené a nelze se na něj spoléhat.
Interfacy verzovat a staré verze držet v původní podobě.
Pro účely zpětné kompatibility se taky zavedly inline namespace. Překladač vidí všechny namespacy s číslem verze, takže manglované názvy jsou pořád stejné. Pokud se zvedne verze, tak se vytvoří nový namespace s vyšším číslem verze a ty předchozí se nechají být. A v headeru se do neverzovaného namespace nainlinuje ten s nejvyšší verzí, takže při překladu se automaticky používá nejnovější verze.
To číslování tříd rozhraní se používá i jinde, i v Javě (setkal jsem se s tím např. v jednom Apachím projektu). Je to takovém ultimátní řešení, ale ne úplně elegantní. Řeší to i zdrojákovou kompatibilitu API. Na úrovni ABI kompatibility mi ale přijde škoda, když se s tím jazyk/platforma nedokáže vyrovnat a neumožňuje prosté přidávání metod, aniž by se rozbilo volání těch původních.
Abych dal konkrétní příklad: máme rozhraní, které bude implementovat někdo cizí a my ho budeme volat (tzn. SPI). V tomto rozhraní je metoda, které předáváme nějaká data. Protože těch parametrů je víc, předáváme je ve formě objektu/struktury. V budoucnu můžou přibýt nová doplňková data, které implementátor rozhraní může ale nemusí využít (číst je). V dalších verzích mu tam dáme prostě něco navíc, co by se mu mohlo hodit. Přirozenou cestou by bylo přidat do této třídy/struktury nové členy či get metody. Tuhle třídu stejně nikdo nedědí (instance vytváříme vždy my a předáváme je tomu SPI), takže nehrozí kolize jmen.