Szablony czy polimorfizm

0

Piszę klasę "kontenerową", co wybrać w przypadku gdy dla pewnych typów zachowanie klasy ma być inne dokładniej mówiąc trochę inne właściwości ma mieć klasa dla liczb całkowitych i trochę inne dla liczb zmiennoprzecinkowych. Klasa ma mieć również możliwość wspierania typów użytkownika.

I teraz zrobić klasę bazową a później np IntContainer, DoubleContainer, DateContainer itd czy szablon klasy? Jeżeli szablon to w jaki sposób najlepiej rozwiązać różne zachowanie niektórych funkcji dla określonych typów? Konkretne specjalizacje?

I jeżeli takie samo zachowanie jest dobre dla wszystkich typów całkowitych jak qint16, qint32, qint64 to wystarczy mi jedna specjalizacja dla któregoś z tych typów i tak samo specjalizacja np tylko dla double?

1

A co powiesz na obie możliwości? Stworzyć klasę szablonową i dziedziczenie z niej?

1

Szablony i polimorfizm się nie wykluczają.

#include <iostream>

template<class T>
struct Base {
     virtual void common() {std::cout << "common\n";};
     virtual void typeDependent(T val) = 0;
};

struct Derived : public Base<uint8_t> {
     void typeDependent(uint8_t val) override {std::cout << (int)val;}
};

int main() {
	Derived der;
	der.common();
	der.typeDependent(2);
	return 0;
}
0

Można i tak i uważacie że jest to w tym przypadku najlepsze? Klasa będzie mocno "matematyczna" dlatego muszę poświęcić więcej uwagi np działaniom na liczbach zmiennoprzecinkowych czy na datach.

Widziałem że np biblioteka boost korzysta z czegoś co nazywa się " template policy class" do częściowej zmiany zachowania klasy szablonu ale nie wiem czy to coś co może mi pomóc a poza tym wygląda to dosyć skomplikowanie.

Rozumiem, że specjalizacje w przypadku który podałeś mogę implementować w .cpp? Chciałbym to zamknąć w postaci biblioteki.

0

Jak dla mnie za dużo ogólników i za mało szczegółów.

Szablony mają kilka dużych wad:

  • są trudne w pisaniu i utrzymaniu (do pisanie porządnych szablonów trzeba być niezłym guru, przykład: gmock)
  • potencjalne błędy kompilacji potrafią być formą łamigłówki
  • bardzo wydłużają kompilację

Jeśli nie czujesz się pewnie z szablonami to ich nie używaj.

0
Pijany Szczur napisał(a):

Można i tak i uważacie że jest to w tym przypadku najlepsze?

Z opisu problemu jaki przedstawiłeś, wg. mnie najlepsze byłoby połączenie podejścia łączonego z podejściem czysto szablonowym....

Otóż jako twórca biblioteki chcesz zapewnić jak najniższy próg wejścia użytkownikowi, podejście z dziedziczeniem jak i łączone (szablony + dziedziczenie) absolutnie Ci tego nie zapewnia. Użytkownik kontenera musi wiedzieć o różnych specjalizacjach zamiast operować na jednym interfejsie. Dla mnie słabo. Dlatego jakbym chciał stworzyć taką bibliotekę, interfejs stworzyłbym całkowicie szablonowy, ale żeby nie kopiować masywnej ilości kodu, w wewnętrznej implementacji użyłbym podejścia łączonego.

#include <iostream>
#include <memory>

/*
	Wewnetrzna implementacja, nie widzialna dla uzytkownika
*/
template<class T>
struct Base {
     virtual void common() {std::cout << "common\n";};
     
     virtual void typeDependent(T val) {
        // 
     	std::cout << "Base call\n";
     }
};

struct Derived : public Base<uint8_t> {
     void typeDependent(uint8_t val) override {std::cout << "Derived call " << (int)val;}
};

/*
   Interfejs biblioteki
*/

template<class T>
struct Container {
	Container()
                  // c++14 std::make_unique
	 : _impl(new Base<T>()) {}
	
	 void common() {_impl->common();};
     
     void typeDependent(T val) {
     	_impl->typeDependent(val);
     }
	
//private
  std::unique_ptr<Base<T>> _impl;
};

template<>
struct Container<uint8_t> {
	Container()
             // c++14 std::make_unique
	 : _impl(new Derived()) {}
	
	 void common() {_impl->common();};
     
     void typeDependent(uint8_t val) {
     	_impl->typeDependent(val);
     }
	
//private
  std::unique_ptr<Derived> _impl;
};

int main() {
	Container<uint8_t> der;
	der.common();
	der.typeDependent(2);
	return 0;
}

Dzięki temu ułatwiasz pracę sobie bo nie musisz kopiować zbyt dużej ilości kodu między specjalizacjami, oraz ułatwiasz pracę użytkownikowi bo nie musi wiedzieć o poszczególnych specjalizacjach.

0

Oczywiście idealnie byłoby gdyby użytkownik mógł tworzyć klasy tak jak kontenery z biblioteki standardowej czyli

Container<qint64> cont;
cont.toMatrix();
// itd.

Twoje rozwiązanie wygląda ok, dzięki za poglądowy kod. Tak czy inaczej muszę napisać specjalizacje dla każdego typu całkowitoliczbowego oraz dla float, double, date-time itd zgadza się? Większość to będzie copy-paste ale jednak muszą istnieć.

Tylko w tym przypadku w jaki sposób użytkownik może dodać wsparcie dla swoich klas? Musiałby utworzyć swoją specjalizację?

1

templates to jest już polimorfizm

0

Pamiętaj o SRP. Mam wrażenie, że starasz się zrobić klasę która ma za dużo odpowiedzialności.

1 użytkowników online, w tym zalogowanych: 0, gości: 1