Lifetime static/global/heap-allocated objektu v C++

Lifetime static/global/heap-allocated objektu v C++
« kdy: 01. 08. 2022, 10:29:38 »
Ahoj,

resim ted jeden problem. Mame objekt tridy, rekneme Sluzby:

Kód: [Vybrat]
class Sluzby
{
   std::shared_ptr<Http> klient;
   std::shared_ptr<DB> databaze;
};

Tento objekt musi zit po celou dobu behu aplikace a musi byt prave jeden. Pristupuje se k objektu tedy takto:

Kód: [Vybrat]
Sluzby *sluzbyGlobal = new Sluzby;

tj. je to globalni promenna, na kterou se nikdy nevola delete. Tj. umre az na uplnem konci s celou heap.

Ma nejaky smysl, aby objekty uvnitr (Http klient a DB databaze) byly std::shared_ptr a ne proste staticky alokovane membery nebo alespon std::unique_ptr?

Chce se po mne, abych predaval vsechny reference na Http a DB pomoci kopirovani shared pointeru kvuli tomu, aby "nahodou" napr Http nebyl automaticky dealokovan drive nez ho neco pouzije.

Ja argumentuju tim, ze neexistuje situace, kdy by takto alokovany objekt mohl byt dealokovan drive nez skonci jakekoliv vlakno v cele aplikaci (i detached). Tudiz, prijde mi efektivnejsi predavat vsude proste Http& klient namisto pomalejsiho std::shared_ptr<Http>.

Pokud vim, kdyz se vyskytne neopravitelna chyba (vyjimka pri zpracovani vyjimky, SIGABRT), program okamzite skonci a neceka se na nejake uvolnovani pameti nebo kdesi cosi.

Dale, nemyslim si, ze by se vlakno mohlo nejakou vzacnou chybou stat detached. Pokud jsou vsechny vlakna non-detached, s koncem programu se nasilne takova vlakna ukonci, ze? Tj. drive pred uvolnenim heap/static objektu.

Tj. proc proste nemit tohle:

Kód: [Vybrat]
class Sluzby
{
   Http klient;
   DB databaze;
   static Sluzby& Get()
   {
      static Sluzby instance;
      return instance;
   }
};

?


Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #1 kdy: 01. 08. 2022, 10:55:58 »
Nechci se vyjadřovat k celkovému desingu (myslím si o tom svoje), ale není mi jasné, proč chceš předělávat shared_ptr na unique_ptr nebo statickou instanci a tím změnit celý interface? Je kopírování shared_ptr reálný problém, profiloval jsi aplikaci a má smysl to předělat kvůli výkonu? Pokud ne, je to předčasná optimalizace a tím i zbytečná práce.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #2 kdy: 01. 08. 2022, 11:33:39 »
Ano, o designu si taky myslim svoje, chci ho zmenit zpusobem jaky navrhuji. Nejde o performance, ale o korektnost a jednoduchost.

Pokud muze byt

Kód: [Vybrat]
Sluzby *sluzbyGlobal = new Sluzby;

dealokovano drive nez skonci kterekoliv vlakno a ja mam napriklad tridu, ktera drzi pointer/referenci na Sluzby:

Kód: [Vybrat]
class MujObjekt
{
   Sluzby& sluzby;

   void foo() { sluzby.klient->post(); }
};

(ano, bohuzel, takhle to v nasi code base je).

tak me std::shared_ptr<Http> nezachrani, protoze uz byl davno dealokovan s celou tridou Sluzby.

Proto se ptam znovu, muze byt globalni/staticka promenna nebo pamet na heap dealokovana drive nez skonci (i detached) vlakna?

Pokud ano, std::shared_ptr<Http> nas nezachrani, pokud ne, nema std::shared_ptr<Http> v tomhle pripade cenu.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #3 kdy: 01. 08. 2022, 13:27:05 »
Tohle řešení:
Kód: [Vybrat]
class Sluzby
{
   Http klient;
   DB databaze;
   static Sluzby& Get()
   {
      static Sluzby instance;
      return instance;
   }
};
není ekvivalentní vytvoření instance Sluzby na heapu, protože C++ nedefinuje pořadí inicializace statických proměnných v různých kompilačních jednotkách. Pokud něco z instance Sluzby bude používat (konstantní) statickou proměnnou z jiné kompilační jednotky, je to undefined behavior. Ono to tam nemusí být teď, ale možná to tam někdo přidá v budoucnu... Osobně nejsem přiznivcem takového řešení, vytvoření instance Sluzby na heapu tímto problémem netrpí.

alex6bbc

  • *****
  • 1 692
    • Zobrazit profil
    • E-mail
Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #4 kdy: 01. 08. 2022, 13:55:33 »
1) jestlize objekt zije celou dobu, klidne muze byt na stacku a muzes predavat jen jeho referenci.
2) proc ten objekt neudelas jako singleton, tovarni metodu co jej jednou vytvori a nazdar.
3) v modernim c++ uz new a delete nepouzivej, pouzij unique_ptr a pokud nechces v objektu nic menit
tak jen predavej do parametru jeho const raw pointer.
4) pokud chce predavat vlastnictvi unique_ptr tak jako parametr hodnotou a vzdy s move


alex6bbc

  • *****
  • 1 692
    • Zobrazit profil
    • E-mail
Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #5 kdy: 01. 08. 2022, 14:17:30 »
jednoduchy priklad, ze i objekt na stacku muze byt "singleton".
sice jde vytvorit mnoho techto objektu, ale funkcni je jen ten prvni vytvoreny pomoci sid=0.
metody jsou ohnute, ze neco delaji jen kdyz je my_id objektu 0, takze jen prvni instance
funguje spravne.


---------------------------------------------------------------------------
// Singleton je na stacku

#include <iostream>

class Singleton
{
public:
    Singleton(int v) : value(v) { Singleton::sid++; my_id = Singleton::sid; }

    void setValue(int v) { if (my_id == 0) value=v; }
    int getValue() { if (my_id > 0) return 0; return value; }
    void print() { if (my_id > 0) { std::cout << "singleton is bad!" << std::endl; return; } std::cout << "singleton my_id=" << my_id << " value=" << value << std::endl; }

    static int sid;
private:
   
    int my_id;
    int value;
};

int Singleton::sid = -1;



int main()
{
    Singleton dobry(1);
    dobry.setValue(7);
    dobry.print();

    Singleton spatny1(1);
    spatny1.setValue(7);
    spatny1.print();

    Singleton spatny2(2);
    spatny2.setValue(77);
    spatny2.print();

    dobry.setValue(17);
    dobry.print();

    spatny2.setValue(66);
    spatny2.print();

    return 0;
}


Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #6 kdy: 01. 08. 2022, 14:23:09 »
Tohle řešení:
Kód: [Vybrat]
class Sluzby
{
   Http klient;
   DB databaze;
   static Sluzby& Get()
   {
      static Sluzby instance;
      return instance;
   }
};
není ekvivalentní vytvoření instance Sluzby na heapu, protože C++ nedefinuje pořadí inicializace statických proměnných v různých kompilačních jednotkách. Pokud něco z instance Sluzby bude používat (konstantní) statickou proměnnou z jiné kompilační jednotky, je to undefined behavior. Ono to tam nemusí být teď, ale možná to tam někdo přidá v budoucnu... Osobně nejsem přiznivcem takového řešení, vytvoření instance Sluzby na heapu tímto problémem netrpí.
Tohle není úplně pravda. U globálních objektů není specifikované pořadí mezi jednotlivými .cpp.
Ale tahle instance se vytvoří při prvním zavolání Get a úklid takových objektů je v opačném pořadí než vznikly.

MujObjekt je asi cajk, protože ta reference se dá nasměrovat na později vzniklý objekt jen divokým prasením. Ale dal bych si bacha na věci, co mají na ty Sluzby ukazatel.

Jinak co se týče vláken, tak c++ vlákna by měla skončit před destrukcí statických a globálních objektů. Ale pokud jde o jiné thready, tak bych byl hodně opatrný až paranoidní. Třeba holé winapi s c++ runtime spolupracuje špatně.
« Poslední změna: 01. 08. 2022, 14:26:50 od Jiří Havel »

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #7 kdy: 01. 08. 2022, 14:44:57 »
jednoduchy priklad, ze i objekt na stacku muze byt "singleton".
sice jde vytvorit mnoho techto objektu, ale funkcni je jen ten prvni vytvoreny pomoci sid=0.
metody jsou ohnute, ze neco delaji jen kdyz je my_id objektu 0, takze jen prvni instance
funguje spravne.
Bacha, singleton pattern má i další věci. Třeba že existuje nějaký globální přístupový bod k tomu jedinečnému objektu. A to ten váš příklad nesplňuje.

Tomu, že si nějaký jeden objekt vytvořím ručně a rozstrkám ho dál třeba přes dependency injection, se obvykle Singleton neříká. A stejně tak bych od něčeho nazvaného singleton nečekal, že půjde jednoduše vytvořit víc instancí, které se akorát budou jinak chovat.

anonacct

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #8 kdy: 01. 08. 2022, 14:45:57 »
Vyhoď ten global a pak se teprve můžeš bavit o čistotě nějakého návrhu.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #9 kdy: 01. 08. 2022, 22:14:18 »
Mne sa návrh v otázke zdá byť ok. Čo sa týka predávania DB a http cez shared pointer, zdá sa mi to zbytočné, keďže volaná funkcia nepotrebuje ďalej zdieľať vlastníctvo DB. Prvá odpoveď v tomto threade o tom hovorí: https://stackoverflow.com/questions/10826541/passing-shared-pointers-as-arguments.

RDa

  • *****
  • 2 785
    • Zobrazit profil
    • E-mail
Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #10 kdy: 01. 08. 2022, 23:55:04 »
Ja vedel ze C++ je slepa cesta :) jen si hezky onanujte, vy uber-programatori.

Na vse ostatni staci takovy jednodychy on-demand generator:

Citace
typ *single() {
  static typ *instance;
  if (!instance) instance = &new typ();
  return instance;
}

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #11 kdy: 02. 08. 2022, 07:10:12 »
jednoduchy priklad, ze i objekt na stacku muze byt "singleton".
sice jde vytvorit mnoho techto objektu, ale funkcni je jen ten prvni vytvoreny pomoci sid=0.
metody jsou ohnute, ze neco delaji jen kdyz je my_id objektu 0, takze jen prvni instance
funguje spravne.
Bacha, singleton pattern má i další věci. Třeba že existuje nějaký globální přístupový bod k tomu jedinečnému objektu. A to ten váš příklad nesplňuje.

Tomu, že si nějaký jeden objekt vytvořím ručně a rozstrkám ho dál třeba přes dependency injection, se obvykle Singleton neříká. A stejně tak bych od něčeho nazvaného singleton nečekal, že půjde jednoduše vytvořit víc instancí, které se akorát budou jinak chovat.

Neni pristupovy bod ta globalni instance? Pokud nepotrebuje lazy inicializaci, nevidim problem.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #12 kdy: 02. 08. 2022, 09:24:37 »
jednoduchy priklad, ze i objekt na stacku muze byt "singleton".
sice jde vytvorit mnoho techto objektu, ale funkcni je jen ten prvni vytvoreny pomoci sid=0.
metody jsou ohnute, ze neco delaji jen kdyz je my_id objektu 0, takze jen prvni instance
funguje spravne.
Bacha, singleton pattern má i další věci. Třeba že existuje nějaký globální přístupový bod k tomu jedinečnému objektu. A to ten váš příklad nesplňuje.

Tomu, že si nějaký jeden objekt vytvořím ručně a rozstrkám ho dál třeba přes dependency injection, se obvykle Singleton neříká. A stejně tak bych od něčeho nazvaného singleton nečekal, že půjde jednoduše vytvořit víc instancí, které se akorát budou jinak chovat.

Neni pristupovy bod ta globalni instance? Pokud nepotrebuje lazy inicializaci, nevidim problem.
Ale alex nepsal o žádné globální instanci. V tom jeho příkladu byl "globální" akorát couter na základě kterého mohl ten nesingleton poznat, že je první. K těm samotným objektům na stacku žádný globální přístup nebyl.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #13 kdy: 02. 08. 2022, 09:58:56 »
Ja vedel ze C++ je slepa cesta :) jen si hezky onanujte, vy uber-programatori.

Na vse ostatni staci takovy jednodychy on-demand generator:

Citace
typ *single() {
  static typ *instance;
  if (!instance) instance = &new typ();
  return instance;
}
Supr, tak sis kopl a navíc taky neodpověděl na to, na co se tazatel ptal.

Mimochodem, ten jednoduchý on demand generátor na všechno rozhodně nestačí. Minimálně proto, že není thread-safe. A tazatel se ptal na thready, takže mu to nejspíš vadit bude.

Re:Lifetime static/global/heap-allocated objektu v C++
« Odpověď #14 kdy: 02. 08. 2022, 11:55:28 »
Ja vedel ze C++ je slepa cesta :) jen si hezky onanujte, vy uber-programatori.

Na vse ostatni staci takovy jednodychy on-demand generator:

Citace
typ *single() {
  static typ *instance;
  if (!instance) instance = &new typ();
  return instance;
}

To céčku je naozaj nutné kontrolovať, či statická premenná vo funkcii bola inicializovaná?

V C++ nie je, jednoducho to prebehne iba pri prvom volaní a platí to aj pre tú alokáciu pamäte.

A od C+11 je to aj thread-safe...