I tak nasza podróż przez dziedziczenie i funkcje wirtualne w C++ dobiega końca. Nie martw się, drogi czytelniku, ponieważ w miarę upływu czasu istnieje wiele innych obszarów C++ do zbadania.
Podsumowanie rozdziału
C++ pozwala ustawić wskaźniki klasy bazowej i odniesienia do obiektu pochodnego. Jest to przydatne, gdy chcemy napisać funkcję lub tablicę, która może współpracować z dowolnym typem obiektu pochodzącego z klasy bazowej.
Bez funkcji wirtualnych wskaźniki klasy bazowej i referencje do klasy pochodnej będą miały dostęp tylko do zmiennych składowych klasy bazowej i wersji funkcji.
Funkcja wirtualna to specjalny typ funkcji, który przekłada się na najbardziej pochodną wersję funkcji (tzw. przesłonięcie), która istnieje pomiędzy klasą bazową a klasą pochodną. Aby funkcja klasy pochodnej mogła zostać uznana za przesłonięcie, musi mieć ten sam podpis i typ zwracany, co funkcja wirtualnej klasy bazowej. Jedynym wyjątkiem są kowariantne typy zwracane, które umożliwiają przesłonięcie zwrócenie wskaźnika lub odwołania do klasy pochodnej, jeśli funkcja klasy bazowej zwraca wskaźnik lub odwołanie do klasy bazowej.
Funkcja, która ma być przesłonięciem, powinna używać specyfikatora przesłonięcia, aby upewnić się, że jest to rzeczywiście przesłonięcie.
Ostatecznego specyfikatora można użyć, aby zapobiec przesłonięciu funkcji lub dziedziczeniu z klasy class.
Jeśli zamierzasz używać dziedziczenia, powinieneś uczynić swój destruktor wirtualnym, tak aby odpowiedni destruktor był wywoływany w przypadku usunięcia wskaźnika do klasy bazowej.
Możesz zignorować wirtualne rozpoznawanie, używając operatora rozpoznawania zakresu do bezpośredniego określenia wersji funkcji, której klasy chcesz żądać: np. base.Base::getName().
Wczesne wiązanie ma miejsce, gdy kompilator napotka bezpośrednie wywołanie funkcji. Kompilator lub linker może bezpośrednio rozwiązać te wywołania funkcji. Późne wiązanie ma miejsce, gdy wywoływany jest wskaźnik funkcji. W takich przypadkach nie można określić, która funkcja zostanie wywołana, aż do czasu wykonania. Funkcje wirtualne korzystają z późnego wiązania i wirtualnej tabeli, aby określić, którą wersję funkcji wywołać.
Korzystanie z funkcji wirtualnych wiąże się z kosztami: wywoływanie funkcji wirtualnych trwa dłużej, a konieczność posiadania wirtualnej tabeli zwiększa rozmiar każdego obiektu zawierającego funkcję wirtualną o jeden wskaźnik.
Funkcję wirtualną można przekształcić w czysto wirtualną/abstrakcyjną, dodając „= 0” na końcu prototypu funkcji wirtualnej. Klasa zawierająca czystą funkcję wirtualną nazywana jest klasą abstrakcyjną i nie można jej utworzyć. Klasa dziedzicząca czyste funkcje wirtualne musi je konkretnie zdefiniować, w przeciwnym razie będzie również uważana za abstrakcyjną. Czyste funkcje wirtualne mogą mieć treść, ale nadal są uważane za abstrakcyjne.
Klasa interfejsu to taka, która nie ma zmiennych składowych i zawiera wszystkie czyste funkcje wirtualne. Nazwy tych klas często zaczynają się od dużej litery.
Wirtualna klasa bazowa to klasa bazowa, która jest zawarta tylko raz, niezależnie od tego, ile razy jest dziedziczona przez obiekt.
Kiedy klasa pochodna jest przypisana do obiektu klasy bazowej, klasa bazowa otrzymuje tylko kopię części bazowej klasy pochodnej. Nazywa się to cięciem obiektu.
Rzutowania dynamicznego można użyć do konwersji wskaźnika na obiekt klasy bazowej na wskaźnik do obiektu klasy pochodnej. Nazywa się to downcastingiem. Nieudana konwersja zwróci wskaźnik zerowy.
Najłatwiejszym sposobem przeciążenia operatora<< dla klas dziedziczonych jest napisanie przeciążonego operatora<< dla najbardziej podstawowej klasy, a następnie wywołanie wirtualnej funkcji członkowskiej w celu wykonania wydruku.
Czas quizu
- Każdy z poniższych programów ma jakiś defekt. Sprawdź każdy program (wizualnie, nie poprzez kompilację) i ustal, co jest nie tak z programem. Dane wyjściowe każdego programu powinny być „Derived”.
1a)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
const char* getName() const { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
const char* getName() const { return "Derived"; }
};
int main()
{
Derived d{ 5 };
Base& b{ d };
std::cout << b.getName() << '\n';
return 0;
}1b)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
virtual const char* getName() const { return "Derived"; }
};
int main()
{
Derived d{ 5 };
Base& b{ d };
std::cout << b.getName() << '\n';
return 0;
}1c)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
const char* getName() override { return "Derived"; }
};
int main()
{
Derived d{ 5 };
Base b{ d };
std::cout << b.getName() << '\n';
return 0;
}1d)
#include <iostream>
class Base final
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
const char* getName() override { return "Derived"; }
};
int main()
{
Derived d{ 5 };
Base& b{ d };
std::cout << b.getName() << '\n';
return 0;
}1e)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
virtual const char* getName() = 0;
};
const char* Derived::getName()
{
return "Derived";
}
int main()
{
Derived d{ 5 };
Base& b{ d };
std::cout << b.getName() << '\n';
return 0;
}1f)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value{ value }
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base{ value }
{
}
virtual const char* getName() { return "Derived"; }
};
int main()
{
auto* d{ new Derived(5) };
Base* b{ d };
std::cout << b->getName() << '\n';
delete b;
return 0;
}2a) Utwórz klasę abstrakcyjną o nazwie Shape. Klasa ta powinna mieć trzy funkcje: czystą wirtualną funkcję drukującą, która pobiera i zwraca std::ostream&, przeciążony operator<< i pusty wirtualny destruktor.
2b) Wyprowadź dwie klasy z Shape: Triangle i Circle. Trójkąt powinien mieć 3 punkty jako członkowie. Okrąg powinien mieć jeden punkt środkowy i promień będący liczbą całkowitą. Zastąp funkcję print(), aby następujący program działał:
int main()
{
Circle c{ Point{ 1, 2 }, 7 };
std::cout << c << '\n';
Triangle t{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }};
std::cout << t << '\n';
return 0;
}To powinno zostać wydrukowane:
Circle(Point(1, 2), radius 7) Triangle(Point(1, 2), Point(3, 4), Point(5, 6))
Oto klasa Point, której możesz użyć:
class Point
{
private:
int m_x{};
int m_y{};
public:
Point(int x, int y)
: m_x{ x }, m_y{ y }
{
}
friend std::ostream& operator<<(std::ostream& out, const Point& p)
{
return out << "Point(" << p.m_x << ", " << p.m_y << ')';
}
};2c) Mając powyższe klasy (Point, Shape, Circle i Triangle), zakończ następujący program:
#include <vector>
#include <iostream>
int main()
{
std::vector<Shape*> v{
new Circle{Point{ 1, 2 }, 7},
new Triangle{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }},
new Circle{Point{ 7, 8 }, 3}
};
// print each shape in vector v on its own line here
std::cout << "The largest radius is: " << getLargestRadius(v) << '\n'; // write this function
// delete each element in the vector here
return 0;
}Program powinien wydrukować następujące informacje:
Circle(Point(1, 2), radius 7) Triangle(Point(1, 2), Point(3, 4), Point(5, 6)) Circle(Point(7, 8), radius 3) The largest radius is: 7
Wskazówka: musisz dodać funkcję getRadius() do Circle i obniżyć Shape* do kółko*, aby uzyskać do niego dostęp.
2d) Dodatkowy kredyt: Zaktualizuj wcześniejsze rozwiązanie, aby użyć std::vector<std::unique_ptr<Shape>>. Pamiętaj, że std::unique_ptr nie można kopiować.
h/t, aby przeczytać ten pomysł surrealcereal.

