Inheritance

Created Wednesday 04 May 2016


Concepts learned in CS170:

1 //Let's say we have a struct:
2 struct Person {
3 	char * name;
4 	double height, weight;
5 	int id;
6 }
7 
8 
9 struct Person {
10 	char * name;
11 	double height, weight;
12 	int id;
13 }
14 
15 //If we didn't have that, this is what we would need:
16 
17 void AddPerson (char *, double, double, int);
18 
19 //Instead, now we can just do the following:
20 
21 void AddPerson (Person const *);
22 
23 /* HOWEVER, BECAUSE STRUCTS DO NOT HAVE ACCESS MODIFIERS,
24    THEY ARE NOT ENCAPSULATED.... THEREFORE WE NEED "PUBLIC, PRIVATE, PROTECTED".*/


Inheritance:

1 /*
2 	The following is an example of a "HAS-A" relationship:
3 
4 	Suppose we are designing a Car object.
5 */
6 
7 class Chassis;
8 class Engine;
9 class Wheel;
10 class SteeringWheel;
11 
12 class Car
13 {class SteeringWheel
14 {
15 	public:
16 		void steer_left();
17 		void steer_right();
18 }
19 
20 /*
21 	Here we have some examples of polymorphism in action:
22 */
23 
24 class BombBird
25 {
26 	public:
27 		void fly() {std::cout<<"Bomb fly\n";}
28 };
29 
30 class FireBird
31 {
32 	public:
33 		void fly() { std::cout<<"Fire fly\n";}
34 };
35 
36 //The following is some really shit code:
37 
38 void MakeAllBirdsFly
39 	(std::vector<BombBird> & bombbirds,
40 	 std::vector<FireBird>& firebirds,
41 	 std::vector<DefaultBird>& defaultbirds)
42 {
43 	//blah blah
44 }


1 class B {...};
2 class D1: public B {..}; //Everyone knows that you are the son of your father
3 class D2: protected B{...}; //Only people who inherit from you know that you are the son of your father
4 class D3: private B{..}; //Only you know that you are the son of your father, not even your 
5 						 //father knows you're his son
6 //Yes, I am aware that this is a screwed up example.
7 
8 class B { public: void foo(); }
9 class D: protected B {}
10 
11 D d;
12 d.foo(); //This code is outside B and D. Compile failure. D is not publicly son of B.
13 void D::goo() { D d; d.foo(); } //Ok.
14 
15 class B {public: void foo(); }
16 class D: private B {}
17 void D::goo() { this->foo(); } //Ok.
18 
19 D d;
20 d.foo(); //This code is outside B and D. Compile failure. D is not publicly son of B
21 void D::goo() {D d; d.foo(); } //Not OK.
22 void D::goo() { this->foo(); } //OK. 

1 class B
2 {
3 public:
4 	void foo();
5 }
6 
7 class D2: protected B
8 {
9 	void goo();
10 }
11 
12 class D4: public D2
13 {
14 public:
15 	void goo();
16 }
17 
18 D4::goo()
19 {
20 	D2 d2;
21 	D4 d4;
22 	d2.foo(); //does NOT work
23 	d4.foo(); //works
24 }

IMPORTANT DIRTY TRICK (or why protected is shit):

If you have something that is protected in a previous descendant, you can use something like

1 public:
2 	using GrandParent::onlyformychildren;

And it will be public. This is because the using namespace is under public, and you can think of it as "casting" (kinda) the protected variable as a publicly accessible variable... This is why protected is almost never used in professional environments. This can be done to cast to private or protected as well. Many ways to die.

Note that if you wanna do this for functions, leave out the parentheses (brackets).

Hiding


Let's say a descendant has a function called nihao(int), and the descendant before that has nihao(), if you were to call child.nihao() it would NOT execute the grandparent's nihao().... instead, it will find the first available one and complain if it doesn't work.

You can fix this by doing child.GrandParent::nihao(), but that's obviously pretty ugly. You can also use the "using" trick (shown above).

An example is shown below:

1 #include <iostream>
2 
3 class GrandParent
4 {
5 public:
6   void nihao() {std::cout<<"GP\n";}
7 };
8 
9 class Parent:public GrandParent
10 {
11 public:
12   void nihao(int x) { std::cout<<"Parent\n";}
13 };
14 
15 class Child:public Parent
16 {
17 public:
18 };
19 
20 int main(void)
21 {
22   Child c;
23   //c.nihao(); // Does not work
24   c.nihao(3); // Works, prints "Parent"
25   c.GrandParent::nihao(); //Works, prints "GP"
26   return 0;
27 }

And a separate example, utilizing the "using" trick:

1 #include <iostream>
2 
3 class GrandParent
4 {
5 public:
6   void nihao() {std::cout<<"GP\n";}
7 };
8 
9 class Parent:public GrandParent
10 {
11 public:
12   using GrandParent::nihao;
13   void nihao(int x) { std::cout<<"Parent\n";}
14 };
15 
16 class Child:public Parent
17 {
18 public:
19 };
20 
21 int main(void)
22 {
23   Child c;
24   c.nihao(); // Works now, prints "GP"
25   return 0;
26 }

In private inheritance, classes can be made from within the class to be used as the parent class, but that's it.

Virtual Functions


Virtual stuff, whether it be for classes or functions, is all done at run-time.

Remember, default copy constructor and copy assignment are shallow copies.

When you do most operators, it is almost always necessary to call the base class' function.

Remember, to utilize the base's copy constructor:

1 D2(D2 const & rhs) : B(rhs)

Remember, copy assignments have to be exception-safe.
What does this mean? It means that if you "new" something, and an exception is thrown, you have to handle it properly and the data in the original class must remain intact.

The following example illustrates this; Do note that tmp_buf is a C-Style string:

1 D2& operator=(D2 const & rhs)
2 {
3 	char *tmp_buf = new char[rhs.mLen];
4 	std::copy(rhs.buf,rhs.buf + rhs.mLen, tmp_buf);
5 	B::operator=(rhs);
6 	mLen = rhs.mLen;
7 	delete [] buf;
8 	buf = tmp_buf;
9 	return *this;
10 }

Upcasts and Downcasts


1 //B derives from D.
2 
3 D d;
4 B b;
5 b=d;
6 
7 //A downcast	
8 D d;
9 B b;
10 d=b;
11 
12 D d;
13 B b;
14 B& rb = d; //ok
15 B* pb = *d; //ok

1 PolarBear pb;
2 Bear& rb = pb;
3 rb.roar(); //Calls Bear's roar not PolarBear's
4 //This is because roar is calculated at run-time not compile-time

Override


override is a keyword that forces the class to create an error whenever a function is not a virtual.

Multiple Inheritance


The key concept of multiple inheritance is similar to a component-based entity system.

Program to an interface (a class that cannot exist, i.e. an abstract base class (usually done via using virtual functions that are all pure), not to an implementation.

Composition Over Inheritance


It is better to use components (composition/aggregation) instead of inheritance. An example is that if the player wants to have a weapon that can keep on switching. It's super useful.

Definition time:
"Aggregation implies a relationship where the child can exist independently of the parent. Example: Class (parent) and Student (child). Delete the Class and the Students still exist. Composition implies a relationship where the child cannot exist independent of the parent."

Programs should be extremely high-level and abstracted.


Virtual Constructors


Covariant Types


1 #include <iostream>
2 
3 class A
4 {
5 public:
6   virtual A * clone() {std::cout<<"Ayy"<<std::endl; return new A(*this);}
7 };
8 
9 class B : public A
10 {
11 public:
12   B * clone() {std::cout<<"Aaaayyyyyy"<<std::endl; return new B(*this);}
13 };
14 
15 int main(void)
16 {
17   A * ab = new B();
18   ab->clone();
19 }

The above executes the B code. So you CAN create virtual functions with different signatures but ONLY if the type is a narrowing conversion.



Backlinks: index:CS225 Notes:Topics