Witam,
zwracam się z prośbą o inspekcję kodu. Głównie zależy mi tym, czy poprawnie używam funkcji wirtualnych i dziedziczenia, bo to, że "działa" nie znaczy, że wszystko gra w kodzie.
wez to wrzuc na jakiegos githuba, nikt nie bedzie zipow sciagal
Klasa Company nie jest jeszcze ruszona, bo chciałem się upewnić, że nie będę powielać błędów.
Na pewno muszą być destruktory wirtualne, żeby kompilator wiedział jak sprzątać po klasach.
W konstruktorach korzystaj z listy inicjalizacyjnej.
class Base
{
// field1
// field2
public:
Base(arg1, arg2)
: field1{ arg1}, field2{ arg2}
{
}
virtual ~Base(){}
};
W klasie dzidziczącej wykorzystaj konstruktor klasy bazowej do inicjalizacji dziedziczonej części obiektu.
class Foo : public Base
{
// fieldFoo1
// fieldFoo2
public:
Foo(arg1, arg2, arg3, arg4)
:Base{arg1, arg2}, fieldFoo1{ arg3 },
fieldFoo2{ arg4 }
{
}
virtual ~Foo(){}
};
Co do użycia funkcji wirtualnych, to wszystkie mogą być takie. C++ pozwala używać wirtualnych, gdy nawet nie są potrzebne. Konieczne jest opatrzenie funkcji slowem virtual
tych funkcji składowych klasy bazowej, które w klasie pochodnej muszą być nadpisane, aby kompilator wiedział co z tym zrobić.
Powiedz coś więcej o projekcie. Jakie są założenia?
Założeniem projektu jest głównie przećwiczenie przeciążania operatorów i poprawne korzystanie z dziedziczenia.
Aby to potrenować, że zrobię klasę bazową Contact i po niej będą dziedziczyć klasa Person i Company. W ten sposób powstałoby coś na kształt książki telefonicznej. Przy okazji zdecydowałem się na użycie vectora do przechowywania kontaktów. Zrezygnowałem z jakiegokolwiek GUI, aby skupić się na pracy ze standardem.
- Warto używać słowa
override
(od C++11). [edit: Ale widzę, że używasz... :)] - Po co w
person.h
dwie deklaracje tego samego (linie 36 i 39)? - Dlaczego
Person
dziedziczy poContact
? Jaki ma to sens i czy to modeluje relację 'jest'? To jest -- czy każda osoba jest kontaktem (w Twoim projekcie i ewentualnym szerszym kontekście)? [edit: Widzę, że na to odpowiedziałeś w poście wyżej... :)] - Co możesz więcej opowiedzieć o sensie metody
newContact
? Bo ja nie do końca rozumiem jej ideę...
koszalek-opalek napisał(a):
- Warto używać słowa
override
(od C++11). [edit: Ale widzę, że używasz... :)]- Po co w
person.h
dwie deklaracje tego samego (linie 36 i 39)?
Chyba był problem z kompilacją, albo podłapłem to na stackoverflow. Jak wrócę do domu, to sprawdzę.
- Co możesz więcej opowiedzieć o sensie metody
newContact
? Bo ja nie do końca rozumiem jej ideę...
Po prostu dodawanie nowego kontaktu do kontenera. Trochę takie "podawaliny" pod interfejs. Planowałem zrobić dodatkową klasę, która by obsłużyła "formularz" w konsoli dodania nowego kontaktu. Nazwa została Conact, bo to była pierwotnie też metoda virtualna, ale w praktyce wyszło, że jak używam więcej parametrów to już nie nadpisuję jej tylko zasłaniam nazwę(?)
BartoSAS napisał(a):
ale w praktyce wyszło, że jak używam więcej parametrów to już nie nadpisuję jej tylko zasłaniam nazwę(?)
Nie rób tak. Albo rób metodę wirtualną, albo metody o różnych nazwach...
Dlaczego -- "Sposób 37. Nigdy nie definiuj ponownie dziedziczonych funkcji niewirtualnych" tutaj: https://helion.pl/ksiazki/c-50-efektywnych-sposobow-na-udoskonalenie-twoich-programow-scott-meyers,cp50sp.htm#format/d
W skrócie: albo funkcja ma inną odpowiedzialność/przeznaczenie -- i wtedy inaczej się nazywa -- albo ma okreslony interfejs i stałą odpowiedzialnośc -- wtedy powinna być wirtualna...
W porządku. To jeszcze takie pyanie jedno - jak nie mam w klasie wskaźników, czy tam tablicy, co ma sprzątać destruktor? Bo w przykładach, które widziałem to są albo wskaźniki usuwane w destruktorze albo jest przykład pokroju klasy Słowo gdzie jest tablica typu char i ona jest czyszczona w destruktorze.
BartoSAS napisał(a):
W porządku. To jeszcze takie pyanie jedno - jak nie mam w klasie wskaźników, czy tam tablicy, co ma sprzątać destruktor? Bo w przykładach, które widziałem to są albo wskaźniki usuwane w destruktorze albo jest przykład pokroju klasy Słowo gdzie jest tablica typu char i ona jest czyszczona w destruktorze.
Nie wiem, co znaczy 'sprzątać destruktor'. :)
Ale jeśli chodzi o destruktor, który nie ma nic do roboty (bo nie ma wskaźników i tym podobnych rzeczy), to potrzebny jest nic nierobiący destruktor wirtualny (virtual ~Klasa() {}
), jeśli po klasie ma być dziedziczenie (czyli jesli nie jest finalna) -- żeby klasa wiedziała, że ma sprzątać po odpowiedniej klasie (potomnej), niekoniecznie po typie wskaźnika/referencji. Dobrze jest tworzyć destruktory wirtualne zawsze.
To Cię jeszcze raz odeślę do ksiązki, którą wspomniałem (polecam, nie jestem od wydawcy :)) -- "Sposób 14. Umieszczaj w klasach bazowych wirtualne destruktory"