Jak skonfigurować klasę reprezentującą interfejs? Czy to tylko abstrakcyjna klasa podstawowa?
c++
inheritance
interface
abstract-class
pure-virtual
Aaron Fischer
źródło
źródło
Odpowiedzi:
Aby rozwinąć odpowiedź bradtgmurray , możesz zrobić jeden wyjątek od listy czysto wirtualnych metod interfejsu, dodając wirtualny destruktor. Umożliwia to przekazanie własności wskaźnika innej stronie bez ujawnienia konkretnej klasy pochodnej. Destruktor nie musi nic robić, ponieważ interfejs nie ma żadnych konkretnych elementów. Definiowanie funkcji jako wirtualnej i wbudowanej może wydawać się sprzeczne, ale zaufaj mi - nie jest.
Nie musisz dołączać ciała do wirtualnego destruktora - okazuje się, że niektóre kompilatory mają problemy z optymalizacją pustego destruktora i lepiej jest użyć domyślnego.
źródło
=0
) destruktora z ciałem. Zaletą jest to, że kompilator może teoretycznie zobaczyć, że vtable nie ma teraz poprawnych elementów, i całkowicie go odrzucić. W przypadku wirtualnego destruktora z ciałem wspomniany destruktor można nazwać (wirtualnie) np. W środku konstrukcji za pomocąthis
wskaźnika (gdy obiekt jest nadalParent
typu), a zatem kompilator musi dostarczyć poprawną tabelę vt. Więc jeśli nie wywołujesz jawnie wirtualnych destruktorówthis
podczas budowy :) możesz zaoszczędzić na rozmiarze kodu.override
słowo kluczowe, aby umożliwić sprawdzanie argumentów czasu kompilacji i zwracanie typu wartości. Na przykład w deklaracji Childvirtual void OverrideMe() override;
Utwórz klasę za pomocą czysto wirtualnych metod. Użyj interfejsu, tworząc inną klasę, która zastąpi te wirtualne metody.
Metoda czysto wirtualna to metoda klasy zdefiniowana jako wirtualna i przypisana do 0.
źródło
override
w C ++ 11Cały powód, dla którego oprócz abstrakcyjnych klas bazowych w C # / Java masz specjalną kategorię typów interfejsów, to fakt, że C # / Java nie obsługuje wielokrotnego dziedziczenia.
C ++ obsługuje wielokrotne dziedziczenie, więc specjalny typ nie jest potrzebny. Abstrakcyjna klasa bazowa bez metod nieabstrakcyjnych (czysto wirtualnych) jest funkcjonalnie równoważna interfejsowi C # / Java.
źródło
Thread
instancją. Wielokrotne dziedziczenie może być złym projektem, a także kompozycją. Wszystko zależy od przypadku.W C ++ nie ma pojęcia „interfejsu” jako takiego. AFAIK, interfejsy zostały po raz pierwszy wprowadzone w Javie, aby obejść brak wielokrotnego dziedziczenia. Ta koncepcja okazała się całkiem użyteczna, a ten sam efekt można osiągnąć w C ++, używając abstrakcyjnej klasy bazowej.
Abstrakcyjna klasa podstawowa to klasa, w której co najmniej jedna funkcja składowa (metoda w języku Java Lingo) jest czystą funkcją wirtualną zadeklarowaną przy użyciu następującej składni:
Nie można utworzyć instancji abstrakcyjnej klasy bazowej, tzn. Nie można zadeklarować obiektu klasy A. Klasy można wywodzić tylko z A, ale każda klasa pochodna, która nie zapewnia implementacji
foo()
, również będzie abstrakcyjna. Aby przestać być abstrakcyjnym, klasa pochodna musi zapewniać implementacje dla wszystkich dziedziczonych funkcji czysto wirtualnych.Należy pamiętać, że abstrakcyjna klasa podstawowa może być czymś więcej niż interfejsem, ponieważ może zawierać elementy danych i funkcje elementów, które nie są czysto wirtualne. Odpowiednikiem interfejsu byłaby abstrakcyjna klasa bazowa bez danych posiadająca wyłącznie funkcje wirtualne.
I, jak zauważył Mark Ransom, abstrakcyjna klasa podstawowa powinna zapewniać wirtualny destruktor, podobnie jak każda inna klasa podstawowa.
źródło
O ile mogłem przetestować, bardzo ważne jest dodanie wirtualnego destruktora. Używam obiektów utworzonych
new
i zniszczonych przy pomocydelete
.Jeśli nie dodasz wirtualnego destruktora w interfejsie, wówczas destruktor odziedziczonej klasy nie zostanie wywołany.
Jeśli uruchomisz poprzedni kod bez
virtual ~IBase() {};
, zobaczysz, że destruktorTester::~Tester()
nigdy nie jest wywoływany.źródło
Moja odpowiedź jest w zasadzie taka sama jak inne, ale myślę, że są dwie inne ważne rzeczy do zrobienia:
Zadeklaruj wirtualny destruktor w swoim interfejsie lub stwórz chroniony nie-wirtualny, aby uniknąć niezdefiniowanych zachowań, jeśli ktoś spróbuje usunąć obiekt typu
IDemo
.Użyj wirtualnego dziedziczenia, aby uniknąć problemów z wielokrotnym dziedziczeniem. (Kiedy korzystamy z interfejsów, częściej występuje wielokrotne dziedziczenie).
I podobnie jak inne odpowiedzi:
Użyj interfejsu, tworząc inną klasę, która zastąpi te wirtualne metody.
Lub
I
źródło
W C ++ 11 można łatwo całkowicie uniknąć dziedziczenia:
W takim przypadku interfejs ma semantykę odniesienia, tzn. Musisz upewnić się, że obiekt przeżywa interfejs (możliwe jest także tworzenie interfejsów z semantyką wartości).
Tego rodzaju interfejsy mają swoje zalety i wady:
Wreszcie, dziedziczenie jest źródłem wszelkiego zła w złożonym projektowaniu oprogramowania. W Sean Parent's Value Semantyka i polimorfizm oparty na pojęciach (wysoce zalecane, wyjaśniono tam lepsze wersje tej techniki) badany jest następujący przypadek:
Załóżmy, że mam aplikację, w której radzę sobie z kształtami polimorficznie za pomocą
MyShape
interfejsu:W swojej aplikacji robisz to samo z różnymi kształtami za pomocą
YourShape
interfejsu:Teraz powiedz, że chcesz użyć niektórych kształtów, które opracowałem w Twojej aplikacji. Koncepcyjnie, nasze kształty mają ten sam interfejs, ale aby moje kształty działały w Twojej aplikacji, musisz rozszerzyć moje kształty w następujący sposób:
Po pierwsze, modyfikowanie moich kształtów może być w ogóle niemożliwe. Co więcej, wielokrotne dziedziczenie prowadzi do kodu spaghetti (wyobraź sobie, że pojawia się trzeci projekt korzystający z
TheirShape
interfejsu ... co się stanie, jeśli wywołają również funkcję rysowaniamy_draw
?).Aktualizacja: Istnieje kilka nowych odniesień na temat polimorfizmu opartego na braku dziedziczenia:
źródło
Circle
klasa to kiepski projekt. WAdapter
takich przypadkach powinieneś użyć wzoru. Przepraszam, jeśli zabrzmi to trochę ostro, ale spróbuj użyć biblioteki z prawdziwego życia, tak jakQt
przed osądzeniem o spadku. Dziedziczenie znacznie ułatwia życie.Adapter
wzoru? Chcę zobaczyć jego zalety.Square
nie ma? Czy znasz? Właśnie dlatego jest oderwany od rzeczywistości. W rzeczywistości, jeśli zdecydujesz się na bibliotekę „MyShape”, możesz dostosować się do jej interfejsu od samego początku. W przykładzie kształtów jest wiele bzdur (jednym z nich jest to, że masz dwieCircle
struktury), ale adapter wyglądałby mniej więcejWszystkie dobre odpowiedzi powyżej. Jedną dodatkową rzeczą, o której powinieneś pamiętać - możesz też mieć czysty wirtualny destruktor. Jedyną różnicą jest to, że nadal musisz ją wdrożyć.
Zmieszany?
Głównym powodem, dla którego chcesz to zrobić, jest udostępnienie metod interfejsu, tak jak ja, ale zastąpienie ich opcjonalnym.
Aby klasa stała się klasą interfejsu, wymaga czystej metody wirtualnej, ale wszystkie metody wirtualne mają domyślne implementacje, więc jedyną metodą na stworzenie czystej wirtualnej jest destruktor.
Reimplementacja destruktora w klasie pochodnej nie jest niczym wielkim - zawsze reimplementuję destruktor, wirtualny lub nie, w moich klasach pochodnych.
źródło
Jeśli używasz kompilatora C ++ firmy Microsoft, możesz wykonać następujące czynności:
Podoba mi się to podejście, ponieważ powoduje znacznie mniejszy kod interfejsu, a generowany rozmiar kodu może być znacznie mniejszy. Zastosowanie novtable usuwa wszystkie odwołania do wskaźnika vtable w tej klasie, więc nigdy nie można bezpośrednio utworzyć jego instancji. Zobacz dokumentację tutaj - nowatorską .
źródło
novtable
standarduvirtual void Bar() = 0;
= 0;
które dodałem). Przeczytaj dokumentację, jeśli jej nie rozumiesz.= 0;
i zakładałem, że był to po prostu niestandardowy sposób robienia dokładnie tego samego.Mały dodatek do tego, co tam jest napisane:
Po pierwsze, upewnij się, że twój destruktor jest również czysto wirtualny
Po drugie, możesz chcieć odziedziczyć wirtualnie (zamiast normalnie) po wdrożeniu, tylko dla dobrych środków.
źródło
Możesz także rozważyć klasy kontraktów zaimplementowane przy użyciu NVI (Non Virtual Interface Pattern). Na przykład:
źródło
Nadal jestem nowy w rozwoju C ++. Zacząłem od Visual Studio (VS).
Jednak wydaje się, że nikt nie wspomniał
__interface
w VS (.NET) . Ja nie bardzo pewna, czy jest to dobry sposób, aby zadeklarować interfejs. Wydaje się jednak, że zapewnia dodatkowe egzekwowanie (wspomniane w dokumentach ). Tak, abyś nie musiał jawnie określaćvirtual TYPE Method() = 0;
, ponieważ zostanie on automatycznie przekonwertowany.Jeśli ktoś ma coś ciekawego na ten temat, prosimy o udostępnienie. :-)
Dzięki.
źródło
Chociaż prawdą
virtual
jest, że jest to de facto standard definiujący interfejs, nie zapominajmy jednak o klasycznym wzorcu podobnym do C, który jest dostarczany z konstruktorem w C ++:Ma to tę zaletę, że można ponownie powiązać środowisko wykonawcze zdarzeń bez konieczności ponownego tworzenia klasy (ponieważ C ++ nie ma składni do zmiany typów polimorficznych, jest to obejście dla klas kameleonów).
Wskazówki:
click
konstruktora potomka.protected
członkowski i miećpublic
odniesienie i / lub getter.if
zmian stanu w kodzie w porównaniu do stanu s, może to być szybsze niżswitch()
es lubif
s (zmiana jest oczekiwana około 3-4if
s, ale zawsze mierz najpierw.std::function<>
na wskaźniki funkcji, może być w stanie zarządzać wszystkimi danych obiektu wewnątrzIBase
. Od tego momentu możesz mieć schematy wartościIBase
(np.std::vector<IBase>
Będą działać). Zauważ, że może to być wolniejsze w zależności od twojego kompilatora i kodu STL; także, że obecne implementacjestd::function<>
mają zwykle narzut, w porównaniu do wskaźników funkcji, a nawet funkcji wirtualnych (może się to zmienić w przyszłości).źródło
Oto definicja
abstract class
standardu c ++n4687
13.4.2
źródło
Wynik: obszar prostokąta: 35 obszar trójkąta: 17
Widzieliśmy, jak klasa abstrakcyjna zdefiniowała interfejs w kategoriach getArea (), a dwie inne klasy zaimplementowały tę samą funkcję, ale z innym algorytmem do obliczania obszaru specyficznego dla kształtu.
źródło