2013-06-11 6 views
5

mam kilka podstawowych drzew dziedziczenia:Jak ustalić, która funkcja jest wywoływana na podstawie typu obiektów w kontenerze?

class Base { 
    virtual double func() = 0; 
    // functionality is not important for the problem 
    // but it's good to know that Base has some virtual functions 
}; 
class DerivedA : public Base { 
    virtual double func() {}; // implementation A 
}; 
class DerivedB : public Base { 
    virtual double func() {}; // implementation B 
}; 

mam pojemnik zawierający wskaźnik albo DerivedA lub DerivedB przypadkach.

void f1(std::vector<Base*> a)() { /* some code */ } 

int main(in, char**) { 
    std::vector<Base*> base_container; 
    f1(base_container); // works fine 
} 

Prawie wszystko działa na bazie, ale mam pewne funkcje, niewymienione w DerivedA lub DerivedB, który jest specyficzny realizacji, ale działa na DerivedA lub DerivedB pojemniku. Załóżmy następujący fragment kodu:

void f2(std::vector<DerivedA*> a)() { /* implementation A specific code */ } 
void f2(std::vector<DerivedB*> a)() { /* implementation B specific code */ } 

Jaką najlepszą praktyką jest wywoływanie prawidłowej funkcji? Wymieniłem kilka możliwych rozwiązań, które przyszły mi do głowy, ale wszystkie mają poważne wady.

  1. Mogę przechowywać wszystkie dane w konkretnych kontenerach implementacyjnych. Jednak f1() nie będzie już działać, ponieważ std::vector<DerivedA*> nie jest dzieckiem std::vector<Base*> (z dobrych powodów!).

  2. Mogłem ręcznie rzucać wszystkie obiekty przez cały czas, ale to jest brzydkie.

  3. mogę ręcznie nazwać właściwą funkcję, która oczekuje pojemnik podstawowy, dając im charakterystyczną nazwę (jak f2_a i f2_b), ale wydaje się brzydki.

  4. Mogę zaimplementować f2 jako specjalizację szablonu funkcji, ale wydaje się to być nadużyciem.

  5. Mogę spróbować, aby funkcja na pojemniku była zależna tylko od funkcji obiektów w kontenerze, a następnie zaimplementować dla nich funkcje przeciążone. Jest to dobre rozwiązanie w kilku przypadkach, ale w moim przypadku jest to funkcja na kontenerze, dająca różne implementacje na pojemniku dla DerivedA i DerivedB, a nie tylko iterowanie zawartości kontenera.

Jak wprowadzić takie rzeczy? Jak mogę uzyskać kontener, który można przekazać zarówno do f1() i f2()?

+5

Co zrobiłby "f2()" dla kontenera, który zawierał dwa obiekty, jeden "DerivedA", a drugi "DerivedB"? –

+0

Czy możesz podać przykład, dlaczego może to być przydatne? Nie widzę tego.Z punktu widzenia OO, wydaje się, że jest brudny. To może być powód, dla którego twoje podejścia okazują się również brzydkie? –

+0

Możesz mieć rację. Jestem w uczeniu maszynowym niché. Tak więc moja baza jest modelem predykcyjnym, podczas gdy klasy pochodne są modelami regresji lub klasyfikacji. Teraz chcę obliczyć pomiary dokładności modeli i różnią się one od klasyfikacji i regresji. – hildensia

Odpowiedz

1

Najlepszym rozwiązaniem jest, aby twoje dziedzictwo reprezentowało podstawienie i aby klasa bazowa miała w pełni ukształtowany interfejs, który wykonuje całą pracę, używając metod wirtualnych do wybrania pochodnych metod klasowych do użycia. Następnie f2 po prostu pobiera wektor wskaźników bazowych.

W przeciwnym razie, jeśli rzeczywiście istnieją specyficzne interfejsy w klasach pochodnych, do których trzeba uzyskać dostęp (a jeśli tak, poświęć chwilę na zastanowienie się nad tym, co zrobiłeś z projektem), najlepszym rozwiązaniem są metody o różnych nazwach, po jednym dla każdego wyprowadzonego typu, z których każdy akceptuje wektor podstawowych wskaźników. Dzięki odpowiednio nazwanym metodom będzie jasne dla użytkownika, jakiego typu oczekuje się.

1

Inną możliwością może być wykonanie visitor. Funkcjonalność oparta na typie pochodnym będzie polegała na przeciążeniu metody visit() odwiedzającego zamiast przeciążonej funkcji działającej na cały kontener. Działa to również w przypadku kontenerów zawierających obie klasy pochodne.

Wadą jest pewna dodana złożoność i zależności.

0

należy stosować dynamic_cast który zwróci null ze złej przemiany i używać

if(dynamic_cast<DerivedA>(ptr)!=null) {...} 
else if(dynamic_cast<DerivedB>(ptr)!=null) {...} 
+1

Przekłada się to na moje rozwiązanie nr. 3, prawda? Ręczne rozdzielanie na właściwą funkcję. – hildensia

+0

jeśli masz jeden kontener, przez który przechodzisz, następnie użyj dynamic_casting, możesz wywołać odpowiednią funkcję, jednak parametr function nie powinien być kontenerem, powinien być wskaźnikiem do konkretnej klasy – aah134

+0

Nie, myślę, że to twoje rozwiązanie Nie. 2. Ta odpowiedź sugeruje wykonanie obsady wewnątrz 'f1()'. – Oktalist

1

To nie ma sensu.

Nie można wywołać funkcji na wektorze typów pochodnych, gdy kontener źródłowy może zawierać dowolny typ.

Będziesz albo musiał podzielić je na dwa wektory, albo za pomocą dynamicznego rzucania, albo sprawdzając jakąś wirtualną funkcję type(). Tylko wtedy będziesz mógł wywoływać te funkcje z pewnością.

Idealną i najczystszą opcją jest Twój numer opcji 5, jest to sposób, w jaki powinien funkcjonować dynamiczny polimorfizm runtime.

0

Szablony (lub mieszanka szablonów i przeciążenie) oferuje jedno możliwe rozwiązanie, chociaż mogą one stać się tutaj nieporęczne.

template <class T> 
void f1(std::vector<T*> a) 
{ 
    // code that doesn't care whether T is Base or DerivedA or DerivedB 
} 

void f2(std::vector<DerivedA*> a) 
{ 
    // code that requires T = DerivedA 
} 
void f2(std::vector<DerivedB*> a) 
{ 
    // code that requires T = DerivedB 
} 

Próbuję przekazać std::vector<Base*> do f2() spowoduje błąd kompilacji.

1
  1. Nie używałbym surowych wskazówek. shared_ptr lub kontener ptr_...<> firmy Boost, aby wymienić kilka alternatyw.
  2. Nie ujawniam rodzaju używanego pojemnika ani faktu, że przedmioty są przechowywane w pojemniku. Oddzielne przechodzenie kontenera od działań na poszczególnych obiektach.
  3. Wdrożyłbym wzorzec oparty na funkcji wyboru na obiekcie z funkcją wirtualną, ponieważ do tego służą funkcje wirtualne.

Teraz wracamy z idealnego świata do naszej niefortunnej planety.

Jeśli masz pod ręką std::vector<Derived*>, po prostu wywołaj poprawną statycznie rozwiązaną funkcję dla std::vector<Derived*>, przeładowaną lub wyraźnie nazwaną, do wyboru.

Jeżeli wszystko co masz to std::vector<Base*> i odgadnąć, że wszystkie wskaźniki przechowywanych tam rzeczywiście wskazują na Derived1*, prawdopodobnie chcesz, aby zweryfikować przypuszczenie jakoś (co, jeśli rzeczywiście masz mieszankę Derived1*, Derived2* i kto wie , może jakiś rodzaj Derived3* który został dodany przez twojego kolegę, kiedy nie patrzyłeś?) Jak to robisz? Po sprawdzeniu możesz użyć dynamic_cast<>, możesz równie dobrze użyć dynamic_cast<> do reszty wszystkiego.

Jeżeli wszystko co masz to std::vector<Base*> i wiesz, a priori, że wszystkie wskaźniki przechowywanych tam faktycznie wskazywać na Derived1*, są prawdopodobnie przechowywane w std::vector<Base*> bez powodu (od bardzo potrzebna informacja o typie jest stracone i musi odtworzyć w brzydki sposób) i powinieneś rozważyć zmianę go na std::vector<Derived1*>.