2008-10-03 13 views
6

Wygląda jakbym miał fundamentalne nieporozumienie o C++: <C++ alternatywy do void * wskaźniki (nie jest to szablony)

Lubię polimorficzny rozwiązanie kontenera. Dziękuję WIĘC, za zwrócenie na to uwagi :)


Mamy więc potrzebę stworzenia stosunkowo ogólnego obiektu typu kontenera. Zdarza się również, że zawiera pewną logikę biznesową. Jednak w tym kontenerze musimy przechowywać zasadniczo dowolne dane - od prymitywnych typów danych po skomplikowane klasy.

W ten sposób można od razu przejść do idei klasy szablonów i zrobić z nią. Zauważyłem jednak, że polimorfizm C++ i szablony nie grają ze sobą dobrze. Ponieważ mamy do czynienia z pewną złożoną logiką, którą będziemy musieli popracować, wolałbym raczej trzymać się albo szablonów, albo polimorfizmu, a nie próbować walczyć z C++, czyniąc je obydwoma.

Wreszcie, biorąc pod uwagę, że chcę zrobić jedno lub drugie, wolałbym polimorfizm. O wiele łatwiej jest reprezentować ograniczenia takie jak "ten kontener zawiera typy porównywalne" - a la java.

Przeniesienie mnie do tematu pytania: Najprościej mówiąc, wyobrażam sobie, że mogę mieć "wirtualny" interfejs "kontenera", który ma coś zbliżonego do "wypychania danych" i popu (void * data) " (dla zapisu, właściwie nie próbuję implementować stosu).

Nie lubię jednak pustki * na najwyższym poziomie, nie wspominając o tym, że podpis będzie się zmieniał za każdym razem, gdy chcę dodać ograniczenie do typu danych, z którymi może pracować konkretny kontener.

Podsumowanie: Mamy stosunkowo złożone pojemniki, które mają różne sposoby odzyskiwania elementów. Chcemy być w stanie zmienić ograniczenia elementów, które mogą wejść do kontenerów. Elementy powinny działać z wieloma rodzajami kontenerów (o ile spełniają ograniczenia danego kontenera).

Edytuj: Powinienem także wspomnieć, że same pojemniki muszą być polimorficzne. To jest mój główny powód, dla którego nie chcę używać szablonowego C++.

Więc - czy powinienem rzucić miłość do interfejsów typu Java i korzystać z szablonów? Czy powinienem używać void * i statycznie rzucać wszystko? Czy powinienem iść z pustą definicją klasy "Element", która nic nie deklaruje i używać jej jako mojej najwyższej klasy w hierarchii "Element"?

Jednym z powodów, dla których uwielbiam przepełnienie stosu jest to, że wiele odpowiedzi dostarcza interesujących informacji na temat innych podejść, których nawet nie brałem pod uwagę. Dlatego z góry dziękuję za uwagi i komentarze.

+0

co masz na myśli "polimorfizm i szablony nie współgrają ze sobą"? –

+0

W szczególności, polimorficzny pojemnik - zapomniałem wspomnieć o tym wymogu. O ile wiem, nie da się tego zrobić ... ale co wiem. – Voltaire

+0

> Potrzebuję, aby same pojemniki były polimorficzne. Poprawiłem odpowiedź odpowiednio. – Lev

Odpowiedz

3

można nie mieć klasę korzeń pojemnik, który zawiera elementy:

template <typename T> 
class Container 
{ 
public: 

    // You'll likely want to use shared_ptr<T> instead. 
    virtual void push(T *element) = 0; 
    virtual T *pop() = 0; 
    virtual void InvokeSomeMethodOnAllItems() = 0; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    iterator begin(); 
    iterator end(); 
public: 
    virtual void push(T *element) {...} 
    virtual T* pop() { ... } 
    virtual void InvokeSomeMethodOnAllItems() 
    { 
     for(iterator currItem = begin(); currItem != end(); ++currItem) 
     { 
      T* item = *currItem; 
      item->SomeMethod(); 
     } 
    } 
}; 

Pojemniki te mogą być następnie przekazywane wokół polimorficznie:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
}; 

class ConcreteItem 
{ 
public: 
    virtual void SomeMethod() 
    { 
     // Do something 
    } 
}; 

void AddItemToContainer(Container<Item> &container, Item *item) 
{ 
    container.push(item); 
} 

... 

List<Item> listInstance; 
AddItemToContainer(listInstance, new ConcreteItem()); 
listInstance.InvokeSomeMethodOnAllItems(); 

Daje to interfejs pojemnik w typu bezpieczny ogólny sposób.

Jeśli chcesz dodać ograniczenia do rodzaju elementów, które mogą być zawarte, można zrobić coś takiego:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
    typedef int CanBeContainedInList; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    typedef typename T::CanBeContainedInList ListGuard; 
    // ... as before 
}; 
+0

Tak właśnie myślałem. Jedynym problemem jest to, że Element jest zasadniczo pustą klasą. O ile mogę powiedzieć, nie mogę nakładać żadnych ograniczeń na to, co konkretnie jest przechowywane w kontenerze, tak naprawdę nie istnieje wspólny interfejs, który mogę wyodrębnić. Ale może nie jest tak źle. – Voltaire

+0

Również .. Nie sądzę, że możesz mieć funkcje wirtualne w szablonie. Kolejny poważny problem;) – Voltaire

+0

Możesz mieć funkcje wirtualne w szablonie. – Lev

5

Polimorfizm i szablony gra się bardzo dobrze razem, jeśli używasz ich poprawnie.

W każdym razie rozumiem, że chcesz przechowywać tylko jeden typ obiektów w każdej instancji kontenera. Jeśli tak, użyj szablonów. Zapobiegnie to omyłkowemu zapisaniu niewłaściwego typu obiektu.

Jeśli chodzi o interfejsy kontenerów: w zależności od projektu, być może będziesz mógł je także szablonować, a następnie będą one miały metody takie jak void push(T* new_element). Pomyśl o tym, co wiesz o obiekcie, gdy chcesz go dodać do kontenera (nieznanego typu). Skąd obiekt będzie pochodził? Funkcja, która zwraca void*? Czy wiesz, że to będzie porównywalne?Przynajmniej, jeśli wszystkie zapisane klasy obiektów są zdefiniowane w kodzie, możesz je wszystkie dziedziczyć po wspólnym przodku, powiedzmy, Storable i używać Storable* zamiast void*.

Teraz, jeśli widzisz, że obiekty będą zawsze dodawane do kontenera za pomocą metody takiej jak void push(Storable* new_element), to naprawdę nie będzie żadnej wartości dodanej w tworzeniu kontenera jako szablonu. Ale wtedy będziesz wiedział, że powinien przechowywać Storables.

+0

Chociaż może to być złe - myślę, że można bezpiecznie założyć, że tak, każdy kontener będzie przechowywać element jednego typu. Potrzebuję jednak, aby same pojemniki były polimorficzne. To właśnie trudno mi o szablony. – Voltaire

+0

"jeśli użyjesz ich poprawnie" = upadek wielu rzekomo dobrych pomysłów. – DarenW

5

Proste jest zdefiniowanie abstrakcyjnej klasy bazowej o nazwie Container i jej podklasę dla każdego rodzaju przedmiotu, który można przechowywać. Następnie możesz użyć dowolnej standardowej klasy kolekcji (std::vector, std::list, itp.) Do przechowywania wskaźników do Container. Pamiętaj, że ponieważ będziesz przechowywać wskaźniki, będziesz musiał obsługiwać ich alokację/dealokację.

Jednak fakt, że potrzebny jest pojedynczy zbiór do przechowywania obiektów o tak wielu różnych typach, wskazuje, że coś może być nie tak z projektem aplikacji. Może lepiej powrócić do logiki biznesowej przed zaimplementowaniem tego super-generycznego kontenera.

13

Możesz użyć standardowego pojemnika o numerze boost::any, jeśli przechowujesz w kontenerze naprawdę arbitralne dane.

To brzmi bardziej jak wolisz coś jak boost::ptr_container gdzie wszystko, co może być przechowywane w pojemniku musi pochodzić z pewnego rodzaju bazy, a sam pojemnik może tylko dać odwołać do typu bazowego.

1

Stosując polimorfizm, w zasadzie pozostaje się z klasą podstawową dla kontenera i klasami pochodnymi dla typów danych. Klasy bazowe/pochodne mogą mieć tyle wirtualnych funkcji, ile potrzebujesz, w obu kierunkach.

Oczywiście oznaczałoby to również konieczność zawijania pierwotnych typów danych w klasach pochodnych. Jeśli zechcesz ponownie rozważyć użycie szablonów, to w tym miejscu chciałbym użyć szablonów. Utwórz jedną klasę pochodną z bazy, która jest szablonem, i użyj tej dla podstawowych typów danych (i innych, w których nie potrzebujesz więcej funkcjonalności niż zapewnia szablon).

Nie zapominaj, że możesz ułatwić sobie życie, wpisując typedefs dla każdego typu szablonowego - szczególnie, jeśli później musisz zmienić jedną z nich w klasę.

3

Po pierwsze, szablony i polimorfizmy są pojęciami ortogonalnymi i dobrze ze sobą współpracują. Następnie, dlaczego chcesz określonej struktury danych? A co ze strukturami STL lub strukturą danych pobudzających (w szczególności pointer containter) nie działa dla ciebie.

Twoje pytanie brzmi, jakbyś niewłaściwie wykorzystywał dziedziczenie w swojej sytuacji. Możliwe jest utworzenie "constraints" na opakowaniach, szczególnie jeśli korzystasz z szablonów. Te ograniczenia mogą wykraczać poza to, co daje twój kompilator i linker. W rzeczywistości jest to bardziej niezręczne w przypadku tego typu dziedziczenia, a błędy są bardziej prawdopodobne w czasie wykonywania.

+1

Sam kontener ma ciekawe właściwości niezależne od przechowywanych w nim danych. Byłoby miło, gdyby był polimorficzny. Może istnieć biblioteka Open Source bliska potrzebom, ale wątpię, żeby pasowała dokładnie do naszych wymagań. – Voltaire

1

Można również zapoznać się The Boost Concept Check Library (BCCL), który został zaprojektowany w celu zapewnienia ograniczenia na parametry szablonów klas szablonowych, w tym przypadku Twoje pojemniki.

I tylko po to, aby powtórzyć to, co powiedzieli inni, nigdy nie miałem problemu z mieszaniem polimorfizmu i szablonów, i zrobiłem z nimi dość skomplikowane rzeczy.

0

Nie musisz rezygnować z interfejsów Java i używać szablonów. Josh's suggestion ogólnego szablonu bazowego Kontener z pewnością pozwoliłby na przepuszczanie polimorficznie kontenerów i ich dzieci, ale dodatkowo można z pewnością zaimplementować interfejsy jako klasy abstrakcyjne, aby były zawartymi elementami. Nie ma powodu, nie można utworzyć klasę abstrakcyjną IComparable jak zasugerował, tak że można mieć polimorficzny funkcję w następujący sposób:

class Whatever 
{ 
    void MyPolymorphicMethod(Container<IComparable*> &listOfComparables); 
} 

Ta metoda może teraz podjąć wszelkie dziecko pojemnika zawierającego dowolną klasę wykonawczą IComparable, więc byłby bardzo elastyczny.