Na stacku vs. na heapu:
A) Lokální proměnné uvnitř funkce (včetně argumentů deklarovaných v prototypu) se házejí na stack. V user space v moderním OS asi není problém, že by na stacku nebylo dost místa - ale je třeba si uvědomit, že věci na stacku mají omezenou životnost. Jakmile skončí dotyčná funkce, její lokální proměnné se vypaří (out of scope), tzn. stack pointer se vrátí o jeden stack frame výš (stack tradičně roste směrem dolů). Pokud jsou lokálně deklarované nějaké skutečné objekty, proběhne v rámci ukončení funkce jejich destruktor.
B) Instance vytvořené operátorem "new" jsou alokovány na heapu. Na takovou novou instanci dostanete pointer. Pokud takový pointer máte deklarovaný jako lokální proměnnou ve funkci (běžná situace) tak samotný pointer samozřejmě skončí s touto funkcí, ale odkazovaná instance na heapu žije dál. Pokud by Vás to téma zajímalo, tak C++ sice nemá garbage collector, ale alokaci různě velkých věcí na heapu řídí tzv. allocator. Přiděluje a uvolňuje místo, vede si přehled o jednotlivých alokacích, snaží se bránit fragmentaci (ve spolupráci s memory managementem OS = záleží i na podvozku).
C) Popravdě mi není z hlavy úplně jasné, kde jsou alokovány proměnné/objekty, deklarované jako globální v konkrétním Cčkovém souboru (translation unit). Patrně se stanou součástí spustitelného binárního souboru (nebo knihovny) a mají tím alokované místo. Tzn. představte si takový objekt jako "bublinu" uvnitř nějakého toho ELF nebo PE-COFF bináru - do RAMky se dostane zavedením toho bináru ke spuštění. Jisté je, že životnost těchto globálně deklarovaných objektů je shodná se životností běžícího executable bináru - pouze se při startu na to před-alokované místo poštve konstruktor a při ukončení destruktor.
Pak jste psal, že si hrajete se strong pointery... schválně zkusím přihodit střípek své naivní tvorby. "Manuálně invazivní" reference counting šablonu a k ní komplementární "chytrý pointer" (taky šablona). Vytvoříte pointer na nějakou svoji instanci obsahující tohoto refcounting parazita, a parazit ví, že na něj někdo odkazuje. Takových pointerů může být víc. Pokud ten smart pointer projde destrukcí, refcount v odkazované instanci se automaticky sníží. Pokud spadne refcount na nulu, proběhne autodestrukce hostitelské instance refcounting parazitem. Pokud byste chtěl nesmrtelnou instanci, buď si naalokujte navrch jeden smart pointer, který nikdy nezničíte, nebo tu referenci prostě explicitně inkrementujte... (Ano, hrozně rád vynalézám kolo.)
Jojo. První krůčky s C++.
Mně osobně se v začátcích hodně líbila klasická poučka, že abstraktní třída musí mít čistě virtuální destruktor, ale musí pro něj mít definované tělo :-) Nebo jak to vlastně bylo... V původní podobě jsem tu poučku našel
tady. Nicméně už Bruce Eckel ve druhém vydání Thinking in C++
tvrdí, že tahle praxe je překonaná, že dle novějšího standardu má být ten destruktor prostě jenom virtuální (s definovaným tělem = tohle platí dál).
Pak se dá dostat do situací, kde bojujete se zacyklenými deklaracemi tříd. Dalo by se ještě pochopit, že pokud B je memberem A, nelze zároveň deklarovat že A je memberem B. Nekonečná rekurzivita vnoření se nekoná. Ale do podobných lapálií se můžete dostat
při použití šablonových tříd. Situaci nepomáhá, že šablony prakticky komplet žijí v hlavičkových souborech, takže nelze použít fintu "v hlavičkách se to jenom nadeklaruje a pak v těle (souboru CPP) se dořeší zbytky". Tuším jsem se asi dvakrát dostal do kouta, ze kterého jsem nenašel jinou cestu, než obalit jednu ze tříd do memberu typu void* :-)
Prostě zažívám okamžiky
C++ WAT :-)
Popravdě mi šly oči trochu křížem z tohoto řádku ve Vašem zdrojáku:
typedef function<void(KeyboardShortcut)> KeyboardCallback;
...ale nakonec pokud správně chápu, nejedná se o nic jiného než
v Céčkové tradiční notaci typedef void (*KeyboardCallback)(KeyboardShortcut);
...čili žádné velké funkcionální utrpení :-)
Já jsem totiž krajně procedurální tvor...