2010-10-20 12 views
5

Potrzebuję wywołać metodę wirtualną dla wszystkich klas pochodnych od danej podstawowej klasy bazowej zaraz po skonstruowaniu obiektu pochodnego. Ale robi to w konstruktorze klasy bazowej spowoduje wywołanie czystego metoda wirtualnaWywołanie wirtualnej metody natychmiast po zakończeniu budowy

Oto uproszczony przykład:

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
     Loader l; 
     load(l); // <-- pure virtual call! 
    } 
    virtual void load(Loader &) = 0; 
}; 

struct Derived: public Base { 
    int value; 
    void load(Loader &l) { 
     value = Loader.get(0); 
    } 
}; 

mogę zadzwonić load w konstruktorze Derived, ale Derived nie mógł wiedzieć, w jaki sposób aby utworzyć program ładujący. Wszelkie pomysły/rozwiązania?

+0

Na czym polega problem? Możesz wywołać metodę czysto wirtualną. –

+2

@Benoit: Nie w konstruktorze. @Vargas: Prawdopodobnie można go zaprojektować lepiej, więc nie masz tej zależności. Na przykład, dlaczego 'load' jest osobną funkcją, która jest wywoływana w konstruktorze? Dlaczego nie pozwolić, aby 'Derived' wczytał własne wartości. – GManNickG

+0

@Benoit: Od konstruktora? !!! To się nazywa niezdefiniowane zachowanie w C++ –

Odpowiedz

2

pomocą wzoru PIMPL:

template<typename T> 
class Pimpl 
{ 
    public: 
     Pimpl() 
     { 
      // At this point the object you have created is fully constructed. 
      // So now you can call the virtual method on it. 
      object.load(); 
     } 
     T* operator->() 
     { 
      // Use the pointer notation to get access to your object 
      // and its members. 
      return &object; 
     } 
    private: 
     T object; // Not technically a pointer 
         // But otherwise the pattern is the same. 
         // Modify to your needs. 
}; 

int main() 
{ 
    Pimpl<Derived> x; 
    x->doStuff(); 
} 
+0

Jak mogę tego użyć i zapobiec tworzeniu Derived w innym miejscu? – Vargas

+1

Utwórz konstruktor jako prywatny. A następnie uczynić Pimpl przyjacielem Derived i Pimpl przyjacielem Derived2 itd. –

+0

Bez potrzeby modyfikowania każdej wyprowadzonej klasy, istnieje sposób? Może jakoś uczynić Pimpl przyjacielem Base? – Vargas

0

Zrobić to z wielu znanych frameworków (takich jak MFC): tworzą wirtualną funkcję członkowską Init() lub Create() i wykonują tam inicjalizację, a następnie mandat w dokumentacji, którą użytkownik nazywa. Wiem, że nie spodoba ci się ten pomysł, ale po prostu nie możesz wywołać metody wirtualnej od konstruktora i oczekiwać, że zachowa się polimorficznie, niezależnie od metod czystość ...

6

Problem polega na tym, że konstrukcja klasy bazowej występuje zanim klasa pochodna zostanie w pełni skonstruowana. Należy też nazwać „Load” z klasy pochodnej, zainicjować throguh inną funkcję składową wirtualny lub utworzyć funkcję pomocnika, aby to zrobić:

Base* CreateDerived() 
{ 
    Base* pRet = new Derived; 
    pRet->Load(); 
    return pRet; 
} 
+0

Ok, udało mi się zajść tak daleko, ale jak mogę zapobiec temu, aby Derived został stworzony, i dlatego był używany bez właściwej inicjalizacji? – Vargas

+0

@Vargas: Dla każdej klasy pochodnej od Base uczyń konstruktora jako prywatny i zdefiniuj 'Base * CreateDerivedX()' i uczyń go przyjacielem. –

+0

@Eugen, czy można to zrobić bez potrzeby zmiany każdej pochodnej klasy? – Vargas

3

C++ FAQ nazywa ten problem DBDI, Dynamiczne wiązanie podczas budowy. Przede wszystkim problemem jest uniknięcie dwufazowej konstrukcji Zła zalecanej tutaj w innych odpowiedziach. To rodzaj "mojego" elementu FAQ - przekonałem Marshalla, by go dodał.

Jednak Marshall bierze to na sobie jest bardzo ogólny (co jest dobre dla FAQ), podczas gdy ja byłam bardziej zainteresowana szczególnym wzorcem projektowania/kodowania.

Tak więc, zamiast wysyłać cię do FAQ, wysyłam Cię na mój własny blog, artykuł "How to avoid post-construction by using Parts Factories", który prowadzi do odpowiedniego artykułu FAQ, ale omawia dogłębnie ten wzorzec.

można po prostu pominąć dwa pierwsze akapity ...

I jakby ciągnęły tam. :-)

Cheers & HTH.,

1

Nie możesz dodać metodę getLoader() w swojej klasie Base tak że DerivedClass konstruktor można nazwać na this dostać Loader? Jako konstruktor klasy DerivedClass będzie wywoływana po klasie Base, która powinna działać poprawnie.

1

Trudno udzielać porad, chyba że powiesz nam, co chcesz osiągnąć, a nie w jaki sposób. Uważam, że zwykle lepiej jest budować takie obiekty z fabryki, które wcześniej wczytują wymagane dane, a następnie przekazują dane do konstruktora obiektu.

0

Istnieje wiele sposobów, aby rozwiązać ten problem, tu jest 1 propozycja montażu w swoim przewidzianego ramach

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
    } 
    Loader & getLoader(); 
private: 
    Loader l; 
}; 

struct Derived: public Base { 
    int value; 
    Derived() { 
     value = getLoader().get(0); 
    } 
}; 
+0

Uwaga: Twój konstruktor 'Derived' nie został poprawnie zadeklarowany. Ponadto, jeśli instancja 'Loader' jest kosztowna i nie powinna być zbyt długo żywa (to znaczy odczytana z pliku XML lub JSON), będzie to stanowić problem. Jednak kciuki w górę za rozwiązanie kompatybilne wstecz! –

+0

Oczywiście czas życia obiektu Loader zmienia się, podnoszą Państwo dobry punkt na potencjalny związany z tym koszt. –

0

To może przyjść trochę późno po innych odpowiedzi, ale będę jeszcze spróbować.

Ty może zaimplementować to bezpiecznie i bez zmiany klas pochodnych. Należy jednak zmienić użyć wszystkich tych klas, które mogą być znacznie gorsze, w zależności od scenariusza. Jeśli nadal projektujesz, może to być opłacalna alternatywa.

Zasadniczo można zastosować curiously recurring template pattern i wprowadzić kod inicjalizacyjny po wywołaniu konstruktora. Co więcej, jeśli zrobisz to tak, jak to napisałem poniżej, możesz nawet ochronić load przed wywołaniem dwa razy.

struct Loader { 
    int get(int index) { return 0; } 
}; 
struct Base { 
    virtual ~Base() {} // Note: don't forget this. 
protected: 
    virtual void load(Loader &) = 0; 
}; 

struct Derived : public Base { 
    int value; 
protected: 
    void load(Loader &l) { 
     value = l.get(0); 
    } 
}; 

template<typename T> 
class Loaded : public T 
{ 
public: 
    Loaded() { 
     Loader l; T::load(l); 
    } 
}; 

int main (int, char **) 
{ 
    Loaded<Derived> derived; 
} 

Szczerze mówiąc, rozważałbym alternatywny projekt, jeśli potrafisz. Przenieść kod z load do swoich konstruktorów i zapewnić ładowarka jako argument odniesienia zalegających w następujący sposób:

struct Derived : public Base { 
    Derived (Loader& loader = Loader()) { ... } 
}; 

ten sposób całkowicie uniknąć tego problemu.

Podsumowanie: do wyboru są następujące:

  1. Jeśli nie są ograniczone przez ograniczenia zewnętrznych i nie mają obszerną bazę kodu w zależności od tego, zmienić swój projekt na coś bezpieczniejszego.
  2. Jeśli chcesz zachować load sposób, w jaki jest i nie zmieniasz zbyt wiele zajęć, ale jesteś skłonny zapłacić cenę za zmianę wszystkich wystąpień, zastosuj CRTP, jak zaproponowano powyżej.
  3. Jeśli chcesz zachować zgodność z istniejącym kodem klienta, musisz zmienić swoje zajęcia, aby korzystać z PIMPL, ponieważ inni sugerują lub żyją z istniejącym problemem.
+0

Ponadto, jeśli chcesz się upewnić, że obiekty nie zostaną utworzone bez wczytania, zadeklaruj konstruktorów jako chronionych. Jest to jednak * nie * bezpieczne, ponieważ inni klienci mogą po prostu czerpać z twojej klasy i robić, co chcą, w tym nie wywoływać 'load' lub wywoływać go dwa razy. –

+0

é: najpierw tylko nit: drugi przykład nie ma "const" (jest to nieprawidłowy kod, jak przedstawiono). Zgadzam się z twoją troską o pozbycie się 2-fazowej konstrukcji. Aby uzyskać bardziej ogólne podejście, zobacz moją wcześniejszą odpowiedź. I/lub FAQ C++. Cheers & hth., –

+0

@Alf: Zostawiłem 'const', ponieważ w instrukcji problemu,' Loader :: get() 'nie było' const'. Ale masz rację, to jest nieprawidłowa składnia i powinienem to powiedzieć. Jeśli chodzi o Twój wpis, sprawdzam również Twojego bloga. Użyłem dokładnie tego samego projektu w tym samym typie biblioteki, więc całkowicie się z tym zgadzam. Opublikowalem te dwie alternatywy, poniewaz uzyskujemy mniej kodu do napisania. –

Powiązane problemy