Skip to content

Commit e66ab25

Browse files
committed
learncpp virtual (pt2)
1 parent 3802dc9 commit e66ab25

1 file changed

Lines changed: 248 additions & 2 deletions

File tree

content/posts/cpp-learn.md

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3396,6 +3396,21 @@ int main()
33963396
- Avoid multiple inheritance unless alternatives lead to more complexity.
33973397

33983398
## 21. Virtual Functions - Polymorphism
3399+
>C++ allows you to set base class pointers and references to a derived object. This is useful when we want to write a function or array that can work with any type of object derived from a base class.
3400+
Without virtual functions, base class pointers and references to a derived class will only have access to base class member variables and versions of functions.
3401+
A virtual function is a special type of function that resolves to the most-derived version of the function (called an override) that exists between the base and derived class. To be considered an override, the derived class function must have the same signature and return type as the virtual base class function. The one exception is for covariant return types, which allow an override to return a pointer or reference to a derived class if the base class function returns a pointer or reference to the base class.
3402+
A function that is intended to be an override should use the override specifier to ensure that it is actually an override.
3403+
The final specifier can be used to prevent overrides of a function or inheritance from a class.
3404+
If you intend to use inheritance, you should make your destructor virtual, so the proper destructor is called if a pointer to the base class is deleted.
3405+
You can ignore virtual resolution by using the scope resolution operator to directly specify which class’s version of the function you want: e.g. base.Base::getName().
3406+
Early binding occurs when the compiler encounters a direct function call. The compiler or linker can resolve these function calls directly. Late binding occurs when a function pointer is called. In these cases, which function will be called can not be resolved until runtime. Virtual functions use late binding and a virtual table to determine which version of the function to call.
3407+
Using virtual functions has a cost: virtual functions take longer to call, and the necessity of the virtual table increases the size of every object containing a virtual function by one pointer.
3408+
A virtual function can be made pure virtual/abstract by adding “= 0” to the end of the virtual function prototype. A class containing a pure virtual function is called an abstract class, and can not be instantiated. A class that inherits pure virtual functions must concretely define them or it will also be considered abstract. Pure virtual functions can have a body, but they are still considered abstract.
3409+
An interface class is one with no member variables and all pure virtual functions. These are often named starting with a capital I.
3410+
A virtual base class is a base class that is only included once, no matter how many times it is inherited by an object.
3411+
When a derived class is assigned to a base class object, the base class only receives a copy of the base portion of the derived class. This is called object slicing.
3412+
Dynamic casting can be used to convert a pointer to a base class object into a pointer to a derived class object. This is called downcasting. A failed conversion will return a null pointer.
3413+
The easiest way to overload operator<< for inherited classes is to write an overloaded operator<< for the most-base class, and then call a virtual member function to do the printing.
33993414
### 21.1. Pointers and references to the base class of derived objects
34003415
- **Pointers, references, and derived classes:** We can not only assign `Derived pointers and references` to `Derived objects`, but also assign `Base pointers and references` to `Derived objects`.
34013416
> **chỉ có thể gọi các hàm/thành viên có trong Base -> need virtual**
@@ -3627,5 +3642,236 @@ int main()
36273642
return 0;
36283643
}
36293644
```
3630-
>If we intend our class to be inherited from, make sure our destructor is virtual and public.
3631-
If we do not intend our class to be inherited from, mark our class as final.
3645+
> If we intend our class to be inherited from, make sure our destructor is virtual and public.
3646+
If we do not intend our class to be inherited from, mark our class as final.
3647+
3648+
### 21.5. Early binding and late binding
3649+
- `early binding (static binding)`: when a direct call is made to a non-member function or a non-virtual member function, the compiler can determine which function definition should be matched to the call.
3650+
- `late binding (or in the case of virtual function resolution, dynamic dispatch)`: when a function call can’t be resolved until runtime.
3651+
- `virtual table` is a lookup table of functions used to resolve function calls in a dynamic/late binding manner.
3652+
> Early binding/static dispatch = direct function call overload resolution
3653+
Late binding = indirect function call resolution
3654+
Dynamic dispatch = virtual function override resolution
3655+
3656+
### 21.6. Pure virtual functions, abstract base classes, and interface classes
3657+
- `Pure virtual (abstract) functions and abstract base classes`(like abstract class/function in java):
3658+
- syntax: assign the function the value 0. e.g. `virtual int getValue() const = 0; // a pure virtual function`
3659+
- the class will becomes and abstract base class, it can not be instantiated. (create a object using abstract base class as the type)
3660+
- the derived class must define a body for the pure virtual function, if not, it will be considered abstract base class as well.
3661+
- Any class with pure virtual functions should also have a virtual destructor
3662+
- `Pure virtual functions with definitions` (like default in java):
3663+
- We can create pure virtual functions that have definitions. Abstract function
3664+
- When providing a definition for a pure virtual function, the definition must be provided separately (not inline)
3665+
- e.g.
3666+
```cpp
3667+
#include <iostream>
3668+
#include <string>
3669+
#include <string_view>
3670+
3671+
class Animal // This Animal is an abstract base class
3672+
{
3673+
protected:
3674+
std::string m_name {};
3675+
3676+
public:
3677+
Animal(std::string_view name)
3678+
: m_name(name)
3679+
{
3680+
}
3681+
3682+
const std::string& getName() const { return m_name; }
3683+
virtual std::string_view speak() const = 0; // note that speak is a pure virtual function
3684+
3685+
virtual ~Animal() = default;
3686+
};
3687+
3688+
std::string_view Animal::speak() const
3689+
{
3690+
return "buzz"; // some default implementation
3691+
}
3692+
3693+
class Dragonfly: public Animal
3694+
{
3695+
3696+
public:
3697+
Dragonfly(std::string_view name)
3698+
: Animal{name}
3699+
{
3700+
}
3701+
3702+
std::string_view speak() const override// this class is no longer abstract because we defined this function
3703+
{
3704+
return Animal::speak(); // use Animal's default implementation
3705+
}
3706+
};
3707+
3708+
int main()
3709+
{
3710+
Dragonfly dfly{"Sally"};
3711+
std::cout << dfly.getName() << " says " << dfly.speak() << '\n';
3712+
3713+
return 0;
3714+
}
3715+
3716+
```
3717+
3718+
- `Interface classes`: is a class that has no member variables, and where all of the functions are pure virtual
3719+
- e.g.
3720+
```cpp
3721+
#include <string_view>
3722+
3723+
class IErrorLog
3724+
{
3725+
public:
3726+
virtual bool openLog(std::string_view filename) = 0;
3727+
virtual bool closeLog() = 0;
3728+
3729+
virtual bool writeError(std::string_view errorMessage) = 0;
3730+
3731+
virtual ~IErrorLog() {} // make a virtual destructor in case we delete an IErrorLog pointer, so the proper derived destructor is called
3732+
};
3733+
```
3734+
3735+
- `Virtual base classes`:
3736+
- are used in virtual inheritance in a way of preventing multiple "instances" of a given class appearing in an inheritance hierarchy when using multiple inheritances. (e.g. we may want only one copy of `PoweredDevice` (contructing) to be shared by both `Scanner` and `Printer`. )
3737+
- using `virtual` keyworld in the inheritance list of the derived class
3738+
- there is only one base object. The base object is shared between all objects in the inheritance tree and it is only constructed once
3739+
- for the constructor of the most derived class, virtual base classes are always created before non-virtual base classes, which ensures all bases get created before their derived classes.
3740+
- the most derived class is responsible for constructing the virtual base class. (`Copier` is responsible for creating `PoweredDevice`)
3741+
- e.g.
3742+
```cpp
3743+
class PoweredDevice
3744+
{
3745+
};
3746+
3747+
class Scanner: virtual public PoweredDevice
3748+
{
3749+
};
3750+
3751+
class Printer: virtual public PoweredDevice
3752+
{
3753+
};
3754+
3755+
class Copier: public Scanner, public Printer
3756+
{
3757+
};
3758+
```
3759+
3760+
### 21.7. Object slicing
3761+
- `slicing` means the assigning of a Derived class object to a Base class object is called object.
3762+
- When a derived class object is assigned to a base class object in C++, the derived class object's extra attributes are sliced off (not considered) to generate the base class object
3763+
- e.g.
3764+
```cpp
3765+
#include <iostream>
3766+
#include <iostream>
3767+
#include <string_view>
3768+
3769+
class Base
3770+
{
3771+
protected:
3772+
int m_value{};
3773+
3774+
public:
3775+
Base(int value)
3776+
: m_value{ value }
3777+
{
3778+
}
3779+
3780+
virtual ~Base() = default;
3781+
3782+
virtual std::string_view getName() const { return "Base"; }
3783+
int getValue() const { return m_value; }
3784+
};
3785+
3786+
class Derived: public Base
3787+
{
3788+
public:
3789+
Derived(int value)
3790+
: Base{ value }
3791+
{
3792+
}
3793+
3794+
std::string_view getName() const override { return "Derived"; }
3795+
};
3796+
3797+
// slicing is much more likely to occur accidentally with
3798+
void printNameAc(const Base base) // note: base passed by value, not reference
3799+
{
3800+
std::cout << "Name is a " << base.getName() << '\n';
3801+
}
3802+
3803+
// slicing here can all be easily avoided by making the function parameter a reference instead of a pass by value
3804+
void printName(const Base& base) // note: base now passed by reference
3805+
{
3806+
std::cout << "Name is a " << base.getName() << '\n';
3807+
}
3808+
3809+
int main()
3810+
{
3811+
Derived derived{ 5 };
3812+
std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';
3813+
3814+
Base& ref{ derived };
3815+
std::cout << "ref is a " << ref.getName() << " and has value " << ref.getValue() << '\n';
3816+
3817+
Base* ptr{ &derived };
3818+
std::cout << "ptr is a " << ptr->getName() << " and has value " << ptr->getValue() << '\n';
3819+
// Because ref and ptr are of type Base, ref and ptr can only see the Base part of derived -- the Derived part of derived still exists, but simply can’t be seen through ref or // ptr. However, through use of virtual functions, we can access the most-derived version of a function.
3820+
3821+
Base base{ derived }; // what happens here?
3822+
std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';
3823+
// only the Base portion of the Derived object is copied. The Derived portion is not
3824+
3825+
// WARNING
3826+
printNameAc(derived); // we expect that this should print "Name is a Derived", but accidentally led to unbehaviour // Derived
3827+
3828+
// OK
3829+
printName(derived);
3830+
return 0;
3831+
}
3832+
3833+
// OUTPUT
3834+
derived is a Derived and has value 5
3835+
ref is a Derived and has value 5
3836+
ptr is a Derived and has value 5
3837+
base is a Base and has value 5
3838+
Name is a Base
3839+
Name is a Derived
3840+
```
3841+
3842+
### 21.8. Dynamic casting
3843+
- Parent -> Child: DownCasting
3844+
- Child -> Parent: UpCasting
3845+
- We can use `dynamic_cast` to perform downcasting.
3846+
- If a dynamic_cast fails, the result of the conversion will be a null pointer.
3847+
- e.g.
3848+
```cpp
3849+
int main()
3850+
{
3851+
Base* b{ getObject(true) };
3852+
3853+
Derived* d{ dynamic_cast<Derived*>(b) }; // use dynamic cast to convert Base pointer into Derived pointer
3854+
3855+
if (d) // make sure d is non-null
3856+
std::cout << "The name of the Derived is: " << d->getName() << '\n';
3857+
3858+
// DOwncasting for references
3859+
Derived apple{1, "Apple"}; // create an apple
3860+
Base& b{ apple }; // set base reference to object
3861+
Derived& d{ dynamic_cast<Derived&>(b) }; // dynamic cast using a reference instead of a pointer
3862+
std::cout << "The name of the Derived is: " << d.getName() << '\n'; // we can access Derived::getName through d
3863+
3864+
delete b;
3865+
3866+
return 0;
3867+
}
3868+
```
3869+
3870+
- We can also use the `static_cast` to do perform downcasting.
3871+
- it will “succeed” even if the Base pointer isn’t pointing to a Derived object.
3872+
- the result will be in undefined behavior
3873+
3874+
- `dynamic_cast vs static_cast`: use `static_cast` unless you’re `downcasting`, in which case `dynamic_cast` is usually a better choice. However, you should also consider avoiding casting altogether and just use `virtual functions`.
3875+
3876+
### 21.8. Printing inherited classes using operator<<
3877+
- Refer to the learncpp.com

0 commit comments

Comments
 (0)