Pro C++ experty.

bjarne

Re:Pro C++ experty.
« Odpověď #45 kdy: 04. 12. 2015, 17:35:17 »
V debugu to nepovoli ani pouzivat primo new, ani alokovat na stacku.

Jenže aktuálně ti to alokaci na stacku pořád umožní například přes implicitní copy kontruktor.

To tam jeste chybi no, takze do makra CORE_START pridam implicitni copy konstruktor jako private. Stejne ty objekty nebudu nikdy kopirovat pres copy konstruktory.

Stejne to nechapu, ze se ten C++ model nevytvoril nebo nejak pozdeji nepredelal tak, aby tam nebyly ty problemy jako ze clovek nemuze pridat dalsi virtualni metodu do tridy ze ktere uz jina dedi apod.. Kdyz nad tim premyslim, tak by stacilo to udelat nejak tak, aby kdyz kompilator generuje kod co pouziva tridu z knihovny, tak nepouzival ty ukazatele do virtualnich tabulek primo, ale nechal to resit nejak tu tridu. Chtelo by to nejaky jiny jazyk, co se taky preklada do nativniho kodu a nevnucuje pouziti GC, ale je udelan lepe.


bjarne

Re:Pro C++ experty.
« Odpověď #46 kdy: 04. 12. 2015, 17:53:51 »

A kdyz ted  v moji aplikaci bude IFoo interface k nejake implementaci FooImpl, ktera bude dedit ze tridy BarImpl, k niz bude to rozhrani IBar, tak by to rozhrani IFoo melo dedit z rozhrani IBar, jenze to uz jsem zase v situaci, kdy pak nemuzu do rozhrani IBar pridat nejakou virtualni metodu. aniz bych nerozbil kod co pouziva IFoo (co jsem pochopil, tak leaf classam muzu virtualni metody pridavat na konec - krome windows, ten to pry nejak muze preorganizovat). Resenim by bylo zkopirovat ty metody z rozhrani IBar do IFoo, ale jako tohle fakt delat nechci. Chapu to spravne?

Můžeš použít moje řešení IInterface (viz výše) ale samozřejmě musíš na to myslet úplně na začátku, kdy každé rozhraní dědí IInterface. To pak každé rozhraní poskytuje funkci getIfc<typ> a každý ten BarImpl nebo FooImpl může na základě požadavku vrátit pointer na rozhraní, které požaduješ. Je to hodně podobné tomu, co dělá ve windows QueryInterface. Akorát QueryInterface vyžaduje transitivitu a reflexivitu, což moje řešení ne. QueryInterface je blíže spíš dynamic_castu, zatímco mé řešení spíš připomíná funkci QueryService, které zahrnuji i QueryInterface.

Pak můžeš napsat

Kód: [Vybrat]
IFoo *foo = main->createFoo();
IBar *p = foo->getIfc<IBar>()

Je v2c9 FooImpl, aby si zařídilo převod z IFoo na IBar. Třeba tak, že bude třída FooBar, která dědí BarImpl a FooImpl

Me se na tom nelibi to, ze i kdyz vim, ze IFoo by melo poskytovat stejne metody co IBar, kdyz FooImpl dedi z BarImpl, tak stejne musim z IFoo nejakym zpusobem tahat to rozhrani IBar a nemuzu ty metody toho IBar rozhrani volat primo pres IFoo. Kdyz bych udelal to rozhrani IFooBar, tak bych ho zase musel tim ctrl+c ctrl+v zpusobem udrzovat konzistentni s IFoo a IBar.

Zkusim zapremyslet nad tim, jestli by se pri dodrzovani nejake konvence nevyplatilo udelat nejaky preprocesor kodu a plugin do nejakeho IDE, ktery by mi umoznoval tyhle veci nejak automatizovat, takze bych treba vytvoril tridu IBar a pak IFoo a na misto dedeni bych pouzil nejake prazdne makro treba class IFoo EXTENDS(IBar) a IDE by podle toho poznalo, ze mi ma intelli-sense pri pouziti ifoo-> nabizet i metody z IBar a preprocesor by zase vedel, ze pri prekladu ma vsechny metody z IBar zkopirovat do IFoo. 

Re:Pro C++ experty.
« Odpověď #47 kdy: 04. 12. 2015, 17:57:05 »
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í.

Pokud by člověku nevadil dynamic_cast, tak by se tomu kopírování šlo vyhnout. Něco na způsob (je to jen nápad):

Privátní třídy:
Kód: [Vybrat]
class base_private
{
  public:
    virtual ~base_private() {}
};

class A_private : public virtual base_private
{
  private:
    int a,b;

  public:
    virtual void metoda1();
    virtual void metoda2();
};

class B_private : public virtual base_private
{
  private:
    int y,x;

  public:
    virtual void metoda3();
};

class C_private : public A_private, public B_private
{
};


Veřejné rozhraní:
Kód: [Vybrat]
class base
{
  protected:
    base_private *implementation;
    base(base_private *implementation) : implementation(implementation) {}
    ~base() { delete implementation; }
};

class A : public virtual base
{
  public:
    A();
    void metoda1();
    void metoda2();
};

class B : public virtual base
{
  public:
    B();
    void metoda3();
};

class C : public A, public B
{
  public:
    C();
};


Implementace do knihovny:
Kód: [Vybrat]
A::A() : base(new A_private())
{
}

void A::metoda1()
{
  (dynamic_cast<A_private*> (implementation))->metoda1();
}

void A::metoda2()
{
  (dynamic_cast<A_private*> (implementation))->metoda2();
}

B::B() : base(new B_private())
{
}

void B::metoda3()
{
  (dynamic_cast<B_private*> (implementation))->metoda3();
}

C::C() : base(new C_private())
{
}


Uživatel pak bude moci používat třídy přímočaře:
Kód: [Vybrat]
void use()
{
    C *c = new C();
    c->metoda3();
}

Sten

Re:Pro C++ experty.
« Odpověď #48 kdy: 04. 12. 2015, 18:08:22 »
Myslim, ze jsem je zacal brat jako blbe reseni po precteni tohohle clanku http://250bpm.com/blog:4. Celkove jsem na to ale narazil
uz x-krat, ze jsou proste resene spatne. Konkretne proc si nepamatuju, v C++ jsem zatim skoro nic nenaprogramoval. Mam jen nejake ty teoreticke znalosti z toho co jsem si procital v prubehu zivota, abych nebyl omezenej jen na ty moderni jazyky jako Java.

Tohle je obecná kritika systému výjimek, ne toho, jak to dělá C++ (ostatně je to tam i napsané). A IMO je to spíš založené na tom, jak on výjimky používá, než že by to byl problém samotného systému. V ukázkách kombinuje vyhazování výjimek a jejich handlování v rámci jedné funkce, kde bude přístup C vždy mnohem lepší (a taky se používá, viz iostreamy v C++), případně kdy chytá výjimky až v main, což je dobré tak akorát na zalogování před pádem (co tam chcete zachraňovat, když se vám vše zdestruovalo?). Ve chvíli, kdy chyba bude probublávat přes pět vnořených funkcí, už to taková sranda neustále volat if (failed) return error; v každé z nich nebude, a když se odchytí v šesté hned po volání, tak to bude úplně stejně čitelné jako ten if (failed) handle_exception();.

Udržovat konzistentní stav není nijak složité, pokud se držíte RAII a SOLID (hlavně S — Single Responsibility), tím se vyhnete problémům se semi-initialized stavy, protože kompilátor za vás vyřeší správná volání destruktorů při výjimce v konstruktoru.

To nerikam, ze je pomalejsi nez instanceof, ale kdyz uz neco delam v tom C++, tak bych chtel, aby to bylo efektivnejsi nez treba v Jave.

Java sama není nějak pomalá či neefektivní, JVM umí JIT a AOT kompilace (GCC a Art ji umí dokonce zkompilovat do nativní binárky) a běh je hodně rychlý. Pomalé je hlavně to, že se jednoduché věci v Javě často implementují padesáti abstraktními rozhraními, které se potom virtuálně provolávají. Ale to by bylo úplně stejně pomalé, kdyby se to dělalo i v C++.

bjarne

Re:Pro C++ experty.
« Odpověď #49 kdy: 04. 12. 2015, 18:13:48 »
To nerikam, ze je pomalejsi nez instanceof, ale kdyz uz neco delam v tom C++, tak bych chtel, aby to bylo efektivnejsi nez treba v Jave.

Java sama není nějak pomalá či neefektivní, JVM umí JIT a AOT kompilace (GCC a Art ji umí dokonce zkompilovat do nativní binárky) a běh je hodně rychlý. Pomalé je hlavně to, že se jednoduché věci v Javě často implementují padesáti abstraktními rozhraními, které se potom virtuálně provolávají. Ale to by bylo úplně stejně pomalé, kdyby se to dělalo i v C++.

Ja prave budu potrebovat hodne volat nativni metody (potrbuju pouzivat OpenGL) a nelibi se mi ten overhead spojeny s prechodem z Java kontextu do JNI kontextu. Koukal jsem na to, kolik tam je kodu, co se provadi pokazde, kdyz se vola nativni metoda a proto nebudu o te Jave uvazovat, protoze to chci mit co nejefektivnejsi. Jinak bych se na C++ vykaslal a delal to v te Jave.


bjarne

Re:Pro C++ experty.
« Odpověď #50 kdy: 04. 12. 2015, 18:18:11 »
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í.

Pokud by člověku nevadil dynamic_cast, tak by se tomu kopírování šlo vyhnout. Něco na způsob (je to jen nápad):

.....


Me se na tom nelibi prave uz to kopirovani tech metod, ze trida A musi mit defakto stejne rozhrani jako APrivate a vsechna volani jenom delegovat na tu privatni tridu. Je to vlastne i docela zhorseni vykonnosti, protoze se zdvojnasobi pocet volani metod.

JSH

Re:Pro C++ experty.
« Odpověď #51 kdy: 04. 12. 2015, 18:42:47 »
Ja prave budu potrebovat hodne volat nativni metody (potrbuju pouzivat OpenGL) a nelibi se mi ten overhead spojeny s prechodem z Java kontextu do JNI kontextu. Koukal jsem na to, kolik tam je kodu, co se provadi pokazde, kdyz se vola nativni metoda a proto nebudu o te Jave uvazovat, protoze to chci mit co nejefektivnejsi. Jinak bych se na C++ vykaslal a delal to v te Jave.
Pokud ti jde až o takovouhle efektivitu, tak bych se osobně zamyslel nad statickým linkováním. Pokud to jenom trochu jde, tak to řeší spoustu problémů.

Re:Pro C++ experty.
« Odpověď #52 kdy: 04. 12. 2015, 19:53:21 »
Me se na tom nelibi prave uz to kopirovani tech metod, ze trida A musi mit defakto stejne rozhrani jako APrivate a vsechna volani jenom delegovat na tu privatni tridu. Je to vlastne i docela zhorseni vykonnosti, protoze se zdvojnasobi pocet volani metod.

Ale zase ti to umožní přidat do třídy A metody, aníž by to narušilo ABI. Jak totiž plánuješ řešit například situaci, že modul byl přeložen vůči tomuto rozhraní:

Kód: [Vybrat]
class A
{
  public:
    virtual void metoda1();
};

class B : public A
{
  public:
    virtual void metoda3();
};

Ale knihovna je novější a používá už takovéto nové rozhraní:

Kód: [Vybrat]
class A
{
  public:
    virtual void metoda1();
    virtual void metoda2();
};

class B : public A
{
  public:
    virtual void metoda3();
};

Protože pokud ten modul obsahuje například kód (new B)->metoda3(), tak při použití nové verze knihovny ti to zavolá A::metoda2(). Ona možná rozdílná velikost instance totiž nebude jediný problém.

bjarne

Re:Pro C++ experty.
« Odpověď #53 kdy: 04. 12. 2015, 20:15:00 »
Me se na tom nelibi prave uz to kopirovani tech metod, ze trida A musi mit defakto stejne rozhrani jako APrivate a vsechna volani jenom delegovat na tu privatni tridu. Je to vlastne i docela zhorseni vykonnosti, protoze se zdvojnasobi pocet volani metod.

Ale zase ti to umožní přidat do třídy A metody, aníž by to narušilo ABI. Jak totiž plánuješ řešit například situaci, že modul byl přeložen vůči tomuto rozhraní:

Kód: [Vybrat]
class A
{
  public:
    virtual void metoda1();
};

class B : public A
{
  public:
    virtual void metoda3();
};

Ale knihovna je novější a používá už takovéto nové rozhraní:

Kód: [Vybrat]
class A
{
  public:
    virtual void metoda1();
    virtual void metoda2();
};

class B : public A
{
  public:
    virtual void metoda3();
};

Protože pokud ten modul obsahuje například kód (new B)->metoda3(), tak při použití nové verze knihovny ti to zavolá A::metoda2(). Ona možná rozdílná velikost instance totiž nebude jediný problém.

Jo ja vim, jsem to psal, ze jsem si pak uvedomil, ze volani tech virtualnich metod nejde pres tabulku importu, jak se bezne volaji nevirtualni metody, kdyz se pouziva funkce z dynamicke knihovny, ale ze se pouzivaji i tak ty pointery do TVM (jsem myslel, ze se pouzivaji nejak az v tom kodu te tridy v knihovne, ze se to volani pres tu tabulku importu nejak deleguje na ty TVM pointery a tak ty pointery do TVM v kodu pluginu nemusim resit).
V tom tvem pripade by to pak zvolilo spatny index do TVM no. Se prave snazim vymyslet, jak tohle resit jinak, nez podobnym zpusobem, jak si ukazoval. Ono to ale asi bez toho zdvojnasobeni poctu volani metod proste nepujde, jak uz jsem pochopil. To bude urcite hrozny vykonostni propad, kdyz se takhle budou vsechny argumenty vkladat na zasobnik dvakrat jakoby dvakrat - jednou pri volani te public metody a pak pri volani te privatni.

Re:Pro C++ experty.
« Odpověď #54 kdy: 04. 12. 2015, 21:52:25 »
V tom tvem pripade by to pak zvolilo spatny index do TVM no. Se prave snazim vymyslet, jak tohle resit jinak, nez podobnym zpusobem, jak si ukazoval. Ono to ale asi bez toho zdvojnasobeni poctu volani metod proste nepujde, jak uz jsem pochopil. To bude urcite hrozny vykonostni propad, kdyz se takhle budou vsechny argumenty vkladat na zasobnik dvakrat jakoby dvakrat - jednou pri volani te public metody a pak pri volani te privatni.

No, ještě by sis mohl udělat vlastní virtuální metody, ale to už by byl docela úlet :)

Kód: [Vybrat]
#include <iostream>

class A
{
  public:
    A();

  private:
    static void metoda1_impl(A* x);
    static void metoda2_impl(A* x);

    class A_table
    {
      public:
        void (*metoda1_ptr)(A* x);
        void (*metoda2_ptr)(A* x);
    };

  protected:
    A_table *A_tbl;

  public:
    inline void metoda1()
    {
        A_tbl->metoda1_ptr(this);
    }

    inline void metoda2()
    {
        A_tbl->metoda2_ptr(this);
    }
};



class B : public A
{
  public:
    B();

  private:
    static void metoda3_impl(B *);
    static void metoda2_impl(B *);  // nová implementace

    class B_table
    {
      public:
        void (*metoda3_ptr)(B *);
    };

  protected:
    B_table *B_tbl;

  public:
    inline void metoda3()
    {
        B_tbl->metoda3_ptr(this);
    }
};



A::A()
{
    A_tbl = new A_table;
    A_tbl->metoda1_ptr = &A::metoda1_impl;
    A_tbl->metoda2_ptr = &A::metoda2_impl;
}

void A::metoda1_impl(A* x)
{
    std::cout << "A::metoda1" << std::endl;
}

void A::metoda2_impl(A* x)
{
    std::cout << "A::metoda2" << std::endl;
}

B::B()
{
    B_tbl = new B_table;
    A_tbl->metoda2_ptr = (void (*)(A *)) &B::metoda2_impl;
    B_tbl->metoda3_ptr = &B::metoda3_impl;
}

void B::metoda3_impl(B* x)
{
    std::cout << "B::metoda3" << std::endl;
}

void B::metoda2_impl(B* x)
{
    std::cout << "B::metoda2" << std::endl;
}


int main()
{
    B *b = new B;
    b->metoda1();
    b->metoda2();
    b->metoda3();

    A *a = new A;
    a->metoda1();
    a->metoda2();

    a = b;
    a->metoda1();
    a->metoda2();
}

Re:Pro C++ experty.
« Odpověď #55 kdy: 05. 12. 2015, 00:15:09 »
Taková poznámka. Mezi interfaci není uplně od věci provádět virtuální dědění. Je třeba si pouze uvědomit, že tím se přidává komplexita, takže si je třeba to rozmyslet. A pak že virtuální dědění je "nakažlivé". Jakmile to někde člověk použije, zpravidla pak musí všechno od toho místa dědit virtuálně, jinak se mu stane, že ho překladač častuje chybama nebo warningama o ambignuitě tříd a o jejich vzáhemném překrývání (expert je na tohle gcc při levelu -wextra)

Virtuální dědění ovšem dělá trošku jiný layout mezi potomkem a předkem.

Normální dědění jen rozšiřuje tabulku virtuálních metod na dalších indexech. Takže pokud má předek 5 metod a potomek 6, pak předek obsadí indexy 0 - 4 a potomek 5 - 10.

Virtuální dědění ale znamená, že předek bude mít svojí tabulky virtuálních adres.  Potomek má pak v tabulce na nultém odkazu offset na předka a následují virtuální metody na indexech 1 - 6.

Když se z pointeru na potomka volá metoda předka, musí se nejprve vyzvednout offset na indexu 0, ten se přičte (odečte) od pointeru a tím se získá adresa tabulky předka a tam se metoda vyhledá. Je tam určitě dvojitá dereference, což může být další zdržení.

Výhodou virtuálního dědění interfaců je, že každý interface je v objektu jen jednou a k přetypování není nutné komplikovaným způsobem postupovat po hierachii jako u normálního dědění, pokud je treba přetypovat na předka, který se v interfacu nachází dvakrát, nebo i vícekrát (protože pak překladač hlásí, že neví, na kterého předka vlastně má ukázat).

Virtuální dědění interfaců je menší zádrhel, než virtuální dědění celých objektů, kde i přístup k member proměnným se děje přes offsety a to i v rámci implementace metod. Navíc do toho vstupují konstruktory, které u virtuálního dědění vyžadují jiný způsob kontrukce než je člověk zvyklej (interfacy by konstruktory mít neměly)