Na początek literalnie odniosę się do kodu który umieściłeś.
Jeśli masz atrybut klasy typu długość, sam sobie odpowiedz na pytanie czy:
1. Może on mieć wartość ujemną.
2. Czy powinien być atrybutem publicznym w innych klasach.
Akurat na to ostatnie pytanie już sobie odpowiedziałeś i klasie Porownywalne, masz ten atrybut prywatny. Jeśli tak jest, to skąd złamanie tej konsekwencji dla atrybutów objetosc i rodzaj_materialu? One także powinny być prywatne.
W 90% przypadków gdy masz klasę z 1 argumentem przekazywanym do konstruktora, chcesz by uniknąć konwersji typu:
Porownywalne p = 12;
Jeśli chcesz jej uniknąć (a przyznasz że powyższa linia w kontekście Porownywalne wygląda .. dziwnie), dodajesz do konstruktora explicit. To nie pozwoli na taką konwersję.
Inicjalizowanie atrybutów klasy, staraj się zawsze wykonywać w liście inicjalizacyjnej. Dla tak prostych typów jak int nie będzie widać szczególnej różnicy, aczkolwiek ten kod,
Porownywalne (int nowa_dlugosc)
{
dlugosc = nowa_dlugosc;
}
.. inicjalizuje atrybut dlugosc, dwa razy. Na początek jest to domyślny konstruktor int, który przypisze wartość 0 (zero) atrybutowi dlugosc, a następnie w ciele konstruktora będziesz miał przypisanie z nowa_dlugosc.
Zgadzam się z kolegą @adrian17 że metoda porownaj(..) wygląda dziwnie. Jeśli jednak jesteś pewien że ma zwracać wynik [-1, 0, 1], to zbędne jest operowanie this przy dostępach do danych bo wystarczy wyłącznie sama nazwa atrybutu oraz odniesienie poprzez inny. Poza tym inny (jako argument funkcji) powinien być przekazany jako stała referencja a sama metoda nie zmieniając stanu obiektu, powinna posiadać atrybut const.
Samo friend jest chyba przez przypadek bo tu jest totalnie zbędne.
Jak docelowo powinna wyglądać ta klasa, IMHO tak:
#include <iostream>
#include <cstddef>
using namespace std;
class Porownywalne
{
private:
size_t dlugosc;
public:
explicit Porownywalne (size_t nowa_dlugosc)
: dlugosc{nowa_dlugosc}
{ }
int porownaj (const Porownywalne &inny) const
{
if(dlugosc == inny.dlugosc) {
return 0;
} else if(dlugosc < inny.dlugosc) {
return 1;
}
return -1;
}
size_t podaj_dlugosc() const
{
return dlugosc;
}
};
Co do klasy kijek... Jest przecież zrozumiałe że jeśli tworzysz kijek, powinien on mieć długość i rodzaj materiału. Jego długość trafi za pośrednictwem konstruktora rodzica do atrybutu dlugosc a rodzaj materiału do atrybutu rodzaj_materialu. Jak poprzednio, rozsądne jest dodanie dostępu do prywatnego pola rodzaju materiału a dodatkowo przesłanie "ciężkiego" argumentu typu std::string, jako stałej referencji.
Stąd klasa kilek (dlaczego z małej litery nie wiem, raczej jest konwencja nazywania obiektów z małych liter a klas z dużej), może IMHO wyglądać tak:
class Kijek :public Porownywalne
{
private:
string rodzaj_materialu;
public:
Kijek(size_t dlugosc, const string& nowy_material)
: Porownywalne{dlugosc}, rodzaj_materialu{nowy_material}
{ }
const string& podaj_rodzaj_materialu() const
{
return rodzaj_materialu;
}
};
Co do pudełka, dziedziczenie z Porownywalne jest dość egzotyczne. Chcesz porównywać pudełka wyłącznie na podstawie długości? W tym kodzie (a odnoszę się do niego literalnie), masz szansę wyłącznie na taką implementację. Mnie osobiście to zastanawia, ale jeśli mam iść tym tropem, to podobnie jak w Kijku. Masz obowiązek podać długość aby inicjować rodzica Porwnywalne.
IMHO może to wyglądać tak:
class Pudelko :public Porownywalne
{
private:
size_t objetosc;
public:
Pudelko(size_t dlugosc, size_t nowa_objetosc)
: Porownywalne{dlugosc}, objetosc{nowa_objetosc}
{ }
const size_t& podaj_objetosc() const
{
return objetosc;
}
};
Po tych poprawkach, kod da się wywołać np. konstruując kijek tak:
int main()
{
Kijek kijek{12, "amelinium"};
std::cout << kijek.podaj_rodzaj_materialu() << '\n';
}