Szablon czy abstrakcyjna klasa bazowa?
Jeśli chcę, aby klasa była adaptowalna i umożliwiała wybieranie różnych algorytmów z zewnątrz -- jaka jest najlepsza implementacja w C++?
Widzę głównie dwie możliwości:
- Użyj abstrakcyjnej klasy bazowej i przekaż konkretny obiekt w
- Użyj szablonu
Oto mały przykład, zaimplementowany w różnych wersjach:
Wersja 1: abstrakcyjna klasa bazowa
class Brake {
public: virtual void stopCar() = 0;
};
class BrakeWithABS : public Brake {
public: void stopCar() { ... }
};
class Car {
Brake* _brake;
public:
Car(Brake* brake) : _brake(brake) { brake->stopCar(); }
};
Wersja 2a: Szablon
template<class Brake>
class Car {
Brake brake;
public:
Car(){ brake.stopCar(); }
};
Wersja 2b: szablon i dziedziczenie prywatne
template<class Brake>
class Car : private Brake {
using Brake::stopCar;
public:
Car(){ stopCar(); }
};
Wychodząc z Javy, jestem oczywiście skłonny zawsze używać wersji 1, ale wersje szablonów wydają się być preferowane często, np. w kodzie STL? Jeśli to prawda, to tylko ze względu na wydajność pamięci itp (brak dziedziczenia, brak wywołań funkcji wirtualnych)?
Zdaję sobie sprawę, że nie ma dużej różnicy między wersją 2A A 2b, zobacz C++ FAQ.
Czy możesz skomentować te możliwości?
9 answers
To zależy od twoich celów. Możesz użyć wersji 1, Jeśli
- zamiar wymiany hamulców samochodu (w czasie pracy)
- zamierzamy przekazać samochód do funkcji nie szablonowych
Ogólnie wolałbym wersję 1 używając polimorfizmu runtime, ponieważ jest on nadal elastyczny i pozwala na to, aby samochód nadal miał ten sam typ: {[2] } jest innym typem niż Car<Nissan>
. Jeśli Twoje cele to świetna wydajność podczas częstego korzystania z hamulców, polecam podejście template. Nawiasem mówiąc, nazywa się to projekt oparty na zasadach. Zapewniasz politykę hamulcową. Przykład ponieważ powiedziałeś, że programujesz w Javie, prawdopodobnie nie jesteś jeszcze zbyt doświadczony w C++. Jeden sposób:
template<typename Accelerator, typename Brakes>
class Car {
Accelerator accelerator;
Brakes brakes;
public:
void brake() {
brakes.brake();
}
}
Jeśli masz wiele zasad, możesz je grupować w ich własną strukturę i przekazać ją, na przykład jako SpeedConfiguration
collecting Accelerator
, Brakes
i jeszcze trochę. W moich projektach staram się zachować dużą ilość szablonów kodu, dzięki czemu mogą być skompilowane raz do własnych plików obiektowych, bez konieczności umieszczania kodu w nagłówkach, ale nadal pozwalające na polimorfizm (za pomocą funkcji wirtualnych). Na przykład, możesz chcieć zachować wspólne dane i funkcje, które kod inny niż szablon będzie prawdopodobnie wywoływał wiele razy w klasie bazowej:
class VehicleBase {
protected:
std::string model;
std::string manufacturer;
// ...
public:
~VehicleBase() { }
virtual bool checkHealth() = 0;
};
template<typename Accelerator, typename Breaks>
class Car : public VehicleBase {
Accelerator accelerator;
Breaks breaks;
// ...
virtual bool checkHealth() { ... }
};
Nawiasem mówiąc, jest to również podejście, którego używają strumienie C++: std::ios_base
zawiera flagi i rzeczy, które nie zależą od typu znaku lub cech, takich jak openmode, flagi formatowania i takie tam, podczas gdy std::basic_ios
jest to szablon klasy, który go dziedziczy. Zmniejsza to również nadmuchiwanie kodu poprzez współdzielenie kodu, który jest wspólny dla wszystkich instancji szablonu klasy.
Przeczytaj wykorzystanie i nadużycia dziedziczenia przez Herb Sutter.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 16:01:45
Zasada kciuka brzmi:
1) jeśli wybór konkretnego typu jest dokonywany w czasie kompilacji, preferuj szablon. Będzie to bezpieczniejsze (błędy czasu kompilacji vs błędy czasu uruchamiania) i prawdopodobnie lepiej zoptymalizowane. 2) jeśli wybór zostanie dokonany w czasie wykonywania (tj. w wyniku działania użytkownika), nie ma wyboru-Użyj funkcji dziedziczenia i wirtualnych.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 17:24:30
Inne opcje:
- Użyj Visitor Pattern (pozwól, aby Zewnętrzny kod działał na twojej klasie).
- Uzewnętrznij część swojej klasy, na przykład poprzez Iteratory, że ogólny kod oparty na iteratorze może na nich działać. Działa to najlepiej, jeśli obiekt jest kontenerem innych obiektów.
- Zobacz także Wzór strategii (w środku znajdują się przykłady c++)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 14:58:12
Szablony są sposobem na to, aby Klasa używała zmiennej, której Typ tak naprawdę nie obchodzi. Dziedziczenie jest sposobem na zdefiniowanie, co klasa jest oparta na jej atrybutach. Jest to pytanie "jest-a" kontra "ma-a".
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 14:57:47
Większość z twoich pytań została już udzielona, ale chciałem rozwinąć ten fragment:
To część tego. Ale innym czynnikiem jest dodatkowe bezpieczeństwo typu. Kiedy traktujeszPochodzę z Javy, jestem naturalnie skłonni zawsze korzystać z wersji 1, ale wersje szablonów wydają się być preferowane często, np. w kodzie STL? Jeśli to prawda, czy to tylko z powodu wydajność pamięci itp. (brak dziedziczenia, żadnych wirtualnych wywołań funkcji)?
BrakeWithABS
jako Brake
, przegrywasz wpisz informacje. Nie wiesz już, że obiekt jest w rzeczywistości BrakeWithABS
. Jeśli jest to parametr szablonu, masz dokładnie dostępny typ, który w niektórych przypadkach może umożliwić kompilatorowi lepsze sprawdzanie typów. Lub może być przydatne w zapewnieniu, że zostanie wywołane prawidłowe przeciążenie funkcji. (jeśli stopCar()
przekaże obiekt hamulca do drugiej funkcji, która może mieć osobne przeciążenie dla BrakeWithABS
, nie będzie wywołana, jeśli użyłeś dziedziczenia, a twoje BrakeWithABS
zostało rzucone do Brake
.
Innym czynnikiem jest to, że pozwala na większą elastyczność. Dlaczego wszystkie implementacje hamulców muszą dziedziczyć z tej samej klasy bazowej? Czy klasa bazowa ma coś do zaoferowania? Jeśli napiszę klasę, która ujawnia oczekiwane funkcje członka, czy nie jest to wystarczająco dobre, aby działać jako hamulec? Często jawne użycie interfejsów lub abstrakcyjnych klas bazowych ogranicza kod bardziej niż jest to konieczne.
(Uwaga, Nie mówię, że szablony powinny być zawsze preferowane rozwiązanie. Istnieją inne obawy, które mogą mieć na to wpływ, od szybkości kompilacji po "to, co programiści z mojego zespołu są zaznajomieni" lub po prostu "to, co preferuję". I czasami, potrzebujesz polimorfizmu uruchomieniowego, w którym to przypadku rozwiązanie szablonu po prostu nie jest możliwe)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 16:00:15
Ta odpowiedź jest mniej lub bardziej poprawna. Jeśli chcesz coś parametryzować w czasie kompilacji-powinieneś preferować szablony. Jeśli chcesz, aby coś parametryzowano w czasie wykonywania, powinieneś preferować nadpisywanie funkcji wirtualnych.
jednak używanie szablonów nie wyklucza wykonania obu (uelastycznienie wersji szablonu):
struct Brake {
virtual void stopCar() = 0;
};
struct BrakeChooser {
BrakeChooser(Brake *brake) : brake(brake) {}
void stopCar() { brake->stopCar(); }
Brake *brake;
};
template<class Brake>
struct Car
{
Car(Brake brake = Brake()) : brake(brake) {}
void slamTheBrakePedal() { brake.stopCar(); }
Brake brake;
};
// instantiation
Car<BrakeChooser> car(BrakeChooser(new AntiLockBrakes()));
Biorąc to pod uwagę, prawdopodobnie nie użyłbym do tego szablonów... Ale to naprawdę osobisty gust.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:42
Abstrakcyjna klasa bazowa ma narzut wirtualnych wywołań, ale ma tę zaletę, że wszystkie klasy pochodne są tak naprawdę klasami bazowymi. Nie tak, gdy używasz szablonów-Car
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-03 14:41:11
Użyj interfejsu, jeśli chcesz jednocześnie obsługiwać różne klasy Break i ich hierarchię.
Car( new Brake() )
Car( new BrakeABC() )
Car( new CoolBrake() )
I nie znasz tej informacji w czasie kompilacji.
Jeśli wiesz, którą przerwę zamierzasz użyć 2b jest właściwym wyborem dla ciebie, aby określić różne klasy samochodów. Hamulec w tym przypadku będzie twój samochód "Strategia" i można ustawić domyślny.
Nie używałbym 2a. zamiast tego możesz dodać statyczne metody do łamania i wywoływania ich bez instancji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-02 14:59:03
Osobiście zawsze wolałabym używać interfejsów zamiast szablonów z kilku powodów:
- błędy kompilacji i linkowania szablonów są czasami tajemnicze
- trudno debugować kod oparty na szablonach (przynajmniej w Visual studio IDE)
- Szablony mogą powiększyć Twoje pliki binarne.
- Szablony wymagają umieszczenia całego kodu w pliku nagłówkowym, co sprawia, że klasa szablonu jest nieco trudniejsza do zrozumienia.
- Szablony są trudne do utrzymania przez początkujący Programiści.
Używam szablonów tylko wtedy, gdy wirtualne tabele tworzą jakiś napowietrzny.
Oczywiście, to tylko moje zdanie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-10 10:42:38