streda, 1. júna 2011

V čom je chyba?

V jednom C++ kóde som spravil chybu a relatívne ma to prekvapilo. Aj keď po tom čo som si uvedomil, že to je chyba tak to bolo hneď zrejmé. Zjednodušene som túto chybu zaniesol do nasledujúceho kódu - nájdete chybu/chyby? Viete povedať čo sa stane bez toho aby ste spustili tento kód? A ako by ste ho opravili?

#include <string.h>

 
class A
{
public:
       A()
       {
              Initialize();
       }

      
~A()
       {
              Deinitialize();
       }

      
virtual void Initialize()
       {
       }

      
virtual void Deinitialize()
       {
       }
};
 
class B : public A
{
public:
       char *m_pMemory;
       B()
       {
              m_pMemory = NULL;
       }
 
       virtual void Initialize()
       {
              m_pMemory = new char[ 200];
              strcpy( m_pMemory, "Pekne nam to funguje\n");
       }
 
       virtual void Deinitialize()
       {
              if ( m_pMemory)
                     delete []m_pMemory;
       }
 
       char *GetMemory()
       {
              return m_pMemory;
       }
};
 
int _tmain(int argc,  _TCHAR* argv[])
{
      
B b;

      
printf( b.GetMemory());
 
       return 0;
}

9 komentárov:

  1. Ahoj. C++ konstrukcie mi vela nehovoria, ale volat virtualnu (public/protected/default alebo non-final) metodu z konstruktoru v Jave nie je dobry napad. Mozno je tu rovnaky problem? Volanie konstruktora B najprv zavola konstruktor A, ktory zavola inicializaciu (B. Initialize, alokacia m_pMemory), a nasledne dobehne kontruktor B ktory m_pMemory zmeni na NULL. Moze byt? Jeden mozny fix je... 1) nevolat virtualnu initialize metodu z konstruktora A, 2) robit inicializaciu priamo v konstruktore.

    OdpovedaťOdstrániť
  2. a) volanie virtuálne metódy v konštruktore triedy - aspoň v .NET je to neodporúčané, pretože sa môže zavolať zlá virtuálna metóda. Neviem ako presne funguje vtable v C++, ale myslím, že problém môže byť rovnaký.

    b) ak by sa aj B.Initialize() zavolala, tak vynulovanie pointeru nastane po Initialize() a teda stratíš referenciu a máš memory leak.

    OdpovedaťOdstrániť
  3. Ano mate v podstate pravdu - aj ked nie je to uplne spravna odpoved. Myslim, ze v jave a C# by to bola spravna odpoved - ale v C++ to je trosku ine - B::Initialize sa tu ani nezavola. V destruktore a konstruktore ked sa volaju virtualne metody tak sa beru virtualne metody z daneho classu a nie tie co by si clovek namyslal :)

    Ono ked to clovek takto vidi v exampli tak ho hned trkne - ale ak ide o obsiahlejsi kod tak tam uz je problem ...

    OdpovedaťOdstrániť
  4. No, však v .NETe to je rovnako. Sa ti nezavolá posledná prepísaná metóda. Preto je v FxCope aj pravidlo, ktoré ti takéto volania nájde a zreportuje chybu.

    OdpovedaťOdstrániť
  5. Len pre zaujimavost, v Jave by sa B. Initialize zavolala. Vzdy sa zavola spravna metoda. V uvedenom priklade by z toho vznikol leak, tak ako napisal Jozo.

    OdpovedaťOdstrániť
  6. Ak som dobre pochopil z http://www.artima.com/cppsource/nevercall.html tak prave c# sa to sprava inac ako v c++. Predpokladam, ze podobne ako java...

    Aj ked to v tej jave povazujem za zvlastne - ako to funguje v jave - inicializuju sa najprv vsetky premenne a az potom idu konstruktory?

    OdpovedaťOdstrániť
  7. Preco zvlastne? Ked sa vola overridnuta metoda, vola sa vzdy "spravna" verzia. Co sa tyka inicializacie, po vytvoreni objektu sa nastavia fieldy na defaultne hodnoty (typicky 0/false/null, okrem pripadov typu private int field = 10), a potom sa pusti konstruktor. Prva vec, co konstruktor musi spravit, je zavolat nejaky konstruktor nadtriedy. Bud to programator napise sam, alebo kompilator zabezpeci toto volanie. Az potom pokracuje zbytok konstruktoru.

    OdpovedaťOdstrániť
  8. v c++ sa inicializuje najprv pamat pre triedu a potom sa postupne volaju construktory ktore inicializuju "svoje" premenne. Takze ak by sa volala nejaka metoda zo zdedenej classy tak by jej premenne este neboli inicializovane ...

    OdpovedaťOdstrániť
  9. cau jozo, este je chyba aj to ze A nema virtualny destruktor.

    OdpovedaťOdstrániť