Inheritance¶
Allow sharing of design for
- Member data
- Member functions
- Interfaces
Advantages:
- extendable
- avoid code duplication
- code reuse
B is a A:
- A: Base/super/parent class
- B: derived/sub/child class
class Employee
{
public:
Employee(const string& _name, const string & _ssn): name(_name), ssn(_ssn){}
void print() const
{
cout << name << endl;
cout << ssn << endl;
}
void print(const string & msg) const {
cout << msg << endl;
print(); // we rewrite the print function, then the child cannot access print from parent!
// Name Hide!
}
protected:
// private: // can not access from child class
const string name;
const string ssn;
}
class Manager: public Employee
{
public:
Manager(const string & _name, const string & _ssn, const string & _title): Employee(_name, _ssn), title(_title) {}
const string & getTitle() const
{
return title;
}
void print() const
{
Employee::print();
cout << title << end;
}
protected:
const string title;
}
Polymorphism¶
If a child class wants to act different with parent class in the same member function, i.e. it depends on the object that calls the function, so we have to introduce some more mechanisms. That is,
-
redefine function in child function.
-
use virtual function.
Here comes an example.
class Brass
{
private:
std::string fullName;
long acctNum;
double balance;
public:
Brass(const std::string & s = "Nullbody", long an = -1, double bal = 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt);
double Balance() const;
virtual void ViewAcct() const;
virtual ~Brass() {}
};
// Brass Plus Account Class
class BrassPlus : public Brass
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.11125);
virtual void ViewAcct() const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; }
void ResetOwes() { owesBank = 0; }
};
Now the following executable program
// create an object
Brass dom("DominicBanker", 11224, 4183.45);
BrassPlus dot("DorothyBanker", 12118, 2592.00);
// call member function for a real object
dom.ViewAcct(); // use Brass::ViewAcct();
dot.ViewAcct(); // use Brassplus::ViewAcct();
Brass & b1_ref = dom;
Brass & b2_ref = dot;
Then which function does the following call?
Compare the difference.
It is good to declare member function virtual
and destructor function virtual
in base class.
There are two ways of calling functions.
up-casting (*&=) | 造型¶
We know cast(类型转换), like
But up-casting is
-
is the act of converting from a derived reference or pointer to a base class reference or pointer.
-
take an object of a derived class as an object of the base one.
(改变了眼光,不改变内容)
Other Items
- encapsulation 封装;包装;
- capsulation 封装;[高分子] 包囊化作用;
- bonding 邦定
void func(C1 obj); // 函数接受基类 C1 的对象
C2 objC2; // objC2 是派生类 C2 的对象
func(objC2); // Up-casting:将 C2 对象作为 C1 对象传递, and slice-off will happen.
Dynamic Binding & virtual function | 绑定和虚函数¶
Two ways of binding
Binding: which function to be called.
-
Static binding: call the function as the code declared, only taking effect on Non-virtual function. This is known at compiling, which is fast.
-
Dynamic binding: call the function according to the actual object, only taking effect on Virtual function. This is only known at running time, which needs a virtual table to achieve this.
a class which has a virtual function: vtable -> table of the address of virtual functions(8 位for 64)(this is formed while compiling)
Notes on virtual function
-
constructor function could not be
virtual
. -
destructor function should be
virtual
, for derived class may have special object the need to be free and destructed. -
If derived class does not redefine virtual function, then the real pointer to derived class object would call function of base.
-
redefine function parameters would cause hiding method. That is, overloaded function defined in derived class would cause virtual function of base to be hidden. See the following code as an example.
// define class of base
class Dwelling {
public:
virtual void showperks(int a) const;
};
// define derived class
class Howel : public Dwelling {
public:
virtual void showperks() const;
};
// use function
Hovel trump;
trump.showperks(); // valid
trump.showperks(2); // invalid, cause function of base has been hidden
- redefine return type of virtual function would cause covariance of return type, which is an exception but valid for polymorphism. See the following code as an example.
class Dwelling {
public:
virtual Dwelling& build(int n);
};
class Hove1 : public Dwelling {
public:
virtual Hove1& build(int n); // same function signature
};
- If virtual function is overloaded in base class, then derived class must redefine all the overloaded function, otherwise would cause non-defined overloaded function to be hidden. See the following code as an example.
class Dwelling {
public:
// three overloaded showperks function
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
};
class Hove1 : public Dwelling {
public:
// redefine three overloaded showperks function
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
};
If you does not need to modify a virtual function, then just use it as base function, like
void Hovel::showperks() const {Dwelling::showperks();}
B b;
A * p = &b;
long long **vp (long long **)p;
// vp is a pointer to *long long, that is, to a pointer of type long long.
void (*pf)() = (void (*)())(**vp);
// pf is a function pointer, which matches the definition of f()
pf(); // “B::f()”
Initialize: A will create A’s vtable but B will than create B’s vtable and change the point to the vtable.
See the following code.
void fr(Brass & rb) { rb.ViewAcct();}
void fp(Brass * pb) { pb->ViewAcct();}
void fv(Brass b) { b.ViewAcct();}
int main() {
Brass b("Billy Bee", 123432, 10000.0);
BrassPlus bp("Betty Beep", 232313, 12345.0);
// dynamic binding
fr(b); // uses Brass::ViewAcct()
fr(bp); // uses BrassPlus::ViewAcct()
// dynamic binding
fp(&b); // uses Brass::ViewAcct()
fp(&bp); // uses BrassPlus::ViewAcct()
// up-casting, caused by static binding
fv(b); // uses Brass::ViewAcct()
fv(bp); // uses Brass::ViewAcct()
return 0;
}
override | 覆盖¶
class A
{
protected:
int i;
public:
A() {i=10;}
virtual void f() {cout<< “A::f()”<< endl;}
}
class B: public A
{
public:
int i;
public:
B(){ i=20; cout<< “B::i” << i <<endl;}
void f() {cout<< “B::f()”<< endl;}
}
cout << sizeof(A) <<“, “ << sizeof(B) << endl;
// not just 4, 8 but a complex one!
- polymorphic variable(* &)
static type dynamic type
Slice off (obj=) | 切片¶
- will copy the content of child object to the parent object while ignoring the extra thins of the child.
The vptr is ignored.
- Never redefine an inherited default parameter value.
- Multiple inheritance
Abstract Base Class | 抽象基类(ABC)¶
Abstract base classes:
- has pure virtual functions
- Cannot be instantiated
WE take Ellipse and Circle as an example. Circle is an ellipse, so we can use inheritance, but this could lead to redundency. Because circle is an ellipse if and only if the semimajor and semiminor axis of an ellipse are equal. That is, member data semimajor
and semiminor
is useless in circle.
It is better to think in terms of data. What data does ellipse and circle both have? Could we create an abstract class the holds the public member data and functions needed for circle and ellipse?
Check the following code.
class BaseEllipse // Abstract base class
{
private:
double x; // x-coordinate of the center
double y; // y-coordinate of the center
public:
// Constructor with default values for x and y coordinates
BaseEllipse(double x0 = 0, double y0 = 0) : x(x0), y(y0) {}
// Virtual destructor to ensure proper cleanup of derived class objects
virtual ~BaseEllipse() {}
// Function to move the center to new coordinates (nx, ny)
// same for circle and ellipse
void Move(int nx, int ny) { x = nx; y = ny; }
// Pure virtual function to calculate the area (must be overridden by derived classes)
// difference for circle and ellipse
virtual double Area() const = 0; // notation for pure virtual function
};
Note that, when a base class has a pure virtual function, it could not be instantiated. Because we might not give a definition in base class, it depends.