Pro C++ experty.

bjarne

Pro C++ experty.
« kdy: 03. 12. 2015, 14:15:35 »
Ahoj,
budu mit třídu exportovanou dynamickou knihovnou, např. class TridaZKnihovny.

Pokud v programu který tuto knihovnu používá vytvořím proměnnou
TridaZKnihovny* instance = new TridaZKnihovny();
tak mi kompilátor vygeneruje kód závislý na velikosti, kterou odvodí třeba z hlavičkového souboru.
Takže když bych updatoval tu dyn. knihovnu, tak mi to může rozbít ten program, protože se velikost toho
typu může změnit.

Teď ta otázka: Potřeboval bych teda zaručit, že new alokouje správnou velikost. V C++ můžete přetížit operátor new.

Nějak takhle:

class TridaZKnihovny {

  void* opeator new(size_t size) { return malloc(sizeof(TridaZKnihovny));  } // ignoruju size

}

Všude jsem našel různý příklady jak někdo přetěžuje ten operátor aby pak alokoval paměť z nějaké předalkovoané oblasti, takže rychlejší alokace, ale nikde jsme neviděl, že by někdo ignoroval ten parametr size a sám si tu velikost určil.

Nemůžu nikde najít, jestli je zaručené, že ten argument size není nějak rozšířen o místo, co potřebuje třeba standardní knihovna pro správu paměti nebo tak něco. Jako kdyby to bylo tak, že size = sizeof(TridaZKnihovny) + dodatecna_pamet, tak to nebude fungovat, protože já pak alokouju jen sizeof(TridaZKnihovny).

Snad mi rozumíte, o co mi jde.


bjarne

Re:Pro C++ experty.
« Odpověď #1 kdy: 03. 12. 2015, 14:17:33 »
Jsem to udělal blbě ta metoda nesmí být inline, jinak by se nemusela vůbec volat a překladač by to nahradil přímo tím kódem. Ale to je jedno.

petr

Re:Pro C++ experty.
« Odpověď #2 kdy: 03. 12. 2015, 14:47:12 »
A k cemu to ma byt dobre ? Resp. co vsechno ma byt soucasti ABI ?
Osobne bych pridal static metodu, napr.:

class TridaZKnihovny
{
   static TridaZKnihovny* create()
   {
       return new TridaZKnihovny();
   }
}

Ale obecne to na zmeny ABI nestaci.

itexpert

Re:Pro C++ experty.
« Odpověď #3 kdy: 03. 12. 2015, 14:49:33 »
Kazdopadne, "new" nema v modernim C++ co pohledavat. Misto toho se pouzivaji smart pointery, ktere pocitaji reference a zaridi dealokaci.

bjarne

Re:Pro C++ experty.
« Odpověď #4 kdy: 03. 12. 2015, 15:00:51 »
Kazdopadne, "new" nema v modernim C++ co pohledavat. Misto toho se pouzivaji smart pointery, ktere pocitaji reference a zaridi dealokaci.

To bych si nebyl tak jistej, protože smart pointery jsou interně implementované pomocí CAS instrukce na dané platformě a to je prostě z principu pomalejší. Rozhodně bych nechtěl mít celý program protkaný CAS interukcema. Bych řekl, že ta CAS operace může být třeba 100x pomalejší než normální kopírování.
Použít, kde je to třeba, ale nepřehánět to.


gamer

Re:Pro C++ experty.
« Odpověď #5 kdy: 03. 12. 2015, 15:04:02 »
Děláš to špatně, dynamická knihovna by měla mít čistě abstraktní interface, např:
Kód: [Vybrat]
class TridaZKnihovny
{
    virtual void metoda1() = 0;
    virtual void metoda2() = 0;
};

a taky by měla mít ta dynamická třída metodu, kterou vytvořiš instanci, něco jako:
Kód: [Vybrat]
std::shared_ptr<TridaZKnihovny> vytvor_tridu_z_knihovny();

Tím zaručíš, že změnou ve zděděné třídě, která dělá implementaci, nerozbiješ ABI. Ale i tak je k ABI potřeba přistupovat opatrně, např. nemůžeš měnit pořadí virtuálních metod, protože to rozbije vtable. Více viz https://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++

bjarne

Re:Pro C++ experty.
« Odpověď #6 kdy: 03. 12. 2015, 15:05:05 »
A k cemu to ma byt dobre ? Resp. co vsechno ma byt soucasti ABI ?
Osobne bych pridal static metodu, napr.:

class TridaZKnihovny
{
   static TridaZKnihovny* create()
   {
       return new TridaZKnihovny();
   }
}

Ale obecne to na zmeny ABI nestaci.

Potřeboval bych vědět, že můžu tímhle upůsobem instancovat třídu z knihovny i když se třída v dalších verzích knihovny může změnit svoji velikost.
Tu statickou metodu create bych právě diky tomuhle neměl potřebovat.

bjarne

Re:Pro C++ experty.
« Odpověď #7 kdy: 03. 12. 2015, 15:12:12 »
Děláš to špatně, dynamická knihovna by měla mít čistě abstraktní interface, např:
Kód: [Vybrat]
class TridaZKnihovny
{
    virtual void metoda1() = 0;
    virtual void metoda2() = 0;
};

a taky by měla mít ta dynamická třída metodu, kterou vytvořiš instanci, něco jako:
Kód: [Vybrat]
std::shared_ptr<TridaZKnihovny> vytvor_tridu_z_knihovny();

Tím zaručíš, že změnou ve zděděné třídě, která dělá implementaci, nerozbiješ ABI. Ale i tak je k ABI potřeba přistupovat opatrně, např. nemůžeš měnit pořadí virtuálních metod, protože to rozbije vtable. Více viz https://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++

Já nevím, proč bych měl mít tu factory metodu. Když přetížím operátor new, tak by to mělo stačit? Berme to teď čistě z hlediska alokace potřebné paměti.
S datovými členy se bude z vnějšku té exportované třídy pracovat jen pomocí getteru/setteru a žádná veřejná metoda nebude nikdy modifikována.
Podle mě takhle je problém čístě v té velikosti při provádění alokace?

Každopádně si pročtu ten KDE dokument.

gamer

Re:Pro C++ experty.
« Odpověď #8 kdy: 03. 12. 2015, 15:21:25 »
Já nevím, proč bych měl mít tu factory metodu. Když přetížím operátor new, tak by to mělo stačit?

Protože to co chceš udělat, je prasárna. Jak víš, že dynamická knihovna používá stejný alokátor, jako aplikace? Jak zaručíš, že se ta tebou vytvořená třída správně inicializuje? Co když bude konstruktor inlinovaný?

nou

Re:Pro C++ experty.
« Odpověď #9 kdy: 03. 12. 2015, 15:24:56 »
Pozrel by som sa ako na to ide Qt ktora ma velmi dobru ABI kompatibilitu. Tam ma kazda trieda len jednu privatnu member premennu a to je pointer na instanciu privatnej triedy ktora drzi vsetko potrebne member premenne. Tym padom je potom velkost public triedy velmi stabilna napriec verziami kniznice. A ziadnu factory metodu nepotrebujete.

bjarne

Re:Pro C++ experty.
« Odpověď #10 kdy: 03. 12. 2015, 15:31:16 »
Já nevím, proč bych měl mít tu factory metodu. Když přetížím operátor new, tak by to mělo stačit?

Protože to co chceš udělat, je prasárna. Jak víš, že dynamická knihovna používá stejný alokátor, jako aplikace? Jak zaručíš, že se ta tebou vytvořená třída správně inicializuje? Co když bude konstruktor inlinovaný?

Ten přetížený new operátor bude implementovat ta třída z té knihovny. Tu architekturu jak se ty knihovny budou vytvářet tak aby co nejlépe fungovaly s aplikací vymýšlím já, tak bych právě potřeboval zajistit to, aby exportované třídy z knihovny mohli měnit svoji velikost a taky strom dědění.

Třeba se do exportované třídy A přidá další datový člen a další bázová třída a mělo by to fungovat bez nutnosti rekompilace kódu na té knihovně závislé.

nou

Re:Pro C++ experty.
« Odpověď #11 kdy: 03. 12. 2015, 15:34:19 »
Qt to ma zhruba takto
Kód: [Vybrat]
// A_p.h
class A_p
{
  int a,b;
  void metoda1();
  void metoda2();
}
// A.h
class A_p;
class A
{
  A_p *d;
public:
  A();
  void metoda1();
  void metoda2();
}
// A.cpp
#include "A.h"
#include "A_p.h"
void A_p::metoda1()
{
}
void A_p::metoda2()
{
}

A::A() : d(new A_p())
{
}
A::~A()
{
  delete d;
}
void A::metoda1()
{
  d->metoda1();
}
void A::metoda2()
{
  d->metoda2();
}

bjarne

Re:Pro C++ experty.
« Odpověď #12 kdy: 03. 12. 2015, 15:35:25 »
Pozrel by som sa ako na to ide Qt ktora ma velmi dobru ABI kompatibilitu. Tam ma kazda trieda len jednu privatnu member premennu a to je pointer na instanciu privatnej triedy ktora drzi vsetko potrebne member premenne. Tym padom je potom velkost public triedy velmi stabilna napriec verziami kniznice. A ziadnu factory metodu nepotrebujete.

Jojo to je opaque pointer nebo se tomu taky říká PIML. Na Qt už jsme koukal, ale to mi nevyhovuje, protože když potřebuju přidat nové rozhraní a rozhodnu se ten privátní typ podědit např. ještě z třídy Context, tak pak musím do té public třídy přidat všechny metody z toho rozhraní a ty metody budu delegovat na ten privátní typ. To je hrozný. Jako už chápu, proč se všechno dělá v Javě, protože C++ je OOP asi jako Java funkcionální.

gamer

Re:Pro C++ experty.
« Odpověď #13 kdy: 03. 12. 2015, 15:35:55 »
Pozrel by som sa ako na to ide Qt ktora ma velmi dobru ABI kompatibilitu. Tam ma kazda trieda len jednu privatnu member premennu a to je pointer na instanciu privatnej triedy ktora drzi vsetko potrebne member premenne. Tym padom je potom velkost public triedy velmi stabilna napriec verziami kniznice. A ziadnu factory metodu nepotrebujete.

Taky to jde, má to nevýhodu, že ke všem memberům se přistupuje přes pointer, což je otravné psát a může to stát nějaký výkon. Virtuální funkce mají výhodu v tom, že performance hit je jen při volání metody, ne při přístupu ke každému memberu, ale zase nevýhodu v tom, že se nesmí rozbít vtable. Podle vkusu každého soudruha.

gamer

Re:Pro C++ experty.
« Odpověď #14 kdy: 03. 12. 2015, 15:38:47 »
Ten přetížený new operátor bude implementovat ta třída z té knihovny.

Takže globální operátor new aplikace se přetíží v nějaké dynamické knihovně? To opravdu není dobrý nápad. Ono to navíc ani pořádně nefunguje: http://stackoverflow.com/questions/1054697/why-isnt-my-new-operator-called