Inheritance
Inheritance
Revision of CS170 Concepts
--------------------
FOR FURTHER DETAILS PLEASE CONSULT inheritance-lecture-notes-week-xxx\inheritance-lecture-notes-xxx.cpp
____________________
Define relationships between objects:
• Has-a
• Is-a
____________________
3 Types of Views
Note: The views are unofficial - they are used to gain a better understanding of the material.
Logic View
“Has -a”
An instance of A contains at least 1 instance of B
class B
{
};
class A
{
B obj;
};
/* A is in a "has-a" relationship with B*/
{
};
class A
{
B obj;
};
/* A is in a "has-a" relationship with B*/
“Is-a”
D “is-a” B
D can be accessed as if D is a B
class D:B
{
}
/*D is in a "is-a" relationship with B */
{
}
/*D is in a "is-a" relationship with B */
Memory View
Important: A and D have the exact memory layout.
Language View
• Syntax
• Types of Inheritance
• Figure out how members access our result.
____________________
3 Access Specifiers for Inheritance
1. Public inheritance
Syntax:
struct B { int x; int y; };
class D : public B
{
void foo()
{
D d;
d.x=10;
d.y=20; /*This is okay.*/
this->x =30;
this->y = this->x;
//All the above are okay.
}
}
D d;
d.x = d.y; //This is also okay even though it's outside the sscope - this is because it is public.
class D : public B
{
void foo()
{
D d;
d.x=10;
d.y=20; /*This is okay.*/
this->x =30;
this->y = this->x;
//All the above are okay.
}
}
D d;
d.x = d.y; //This is also okay even though it's outside the sscope - this is because it is public.
class D: public B { ... };
D can be publicly used as if it is a B.
Any scope can use D as if it is a B.
2. Protected inheritance
• Protected inheritance can only be accessed by the derived class and it's descendants.
Syntax:
class D2: protected B {};
class D3: protected B { D3 d3; d3.x = d3.y; /*this is okay*/
D2 d2; d2.x = d2.y; /* this is not okay! */
}
//Explanation: D3 is using B even though protected inheritance means that x and y can only be accessed by the derived class and its descendants; neither of whom are D3.
//Another note: D3 cannot be created because D3 has not yet been defined but that's a nitpick.
class D4: protected D2 {D4 d4; d4.x=d4.y //this is okay
D2 d2; d2.x = d2.y; //not okay! Since we're dealing with x and y D4 can only use itself as B but it cannot use D2 as a B.
}
D2 d2;
d2.x = d2.y; /*This does NOT WORK, results in compile time failure */
class D3: protected B { D3 d3; d3.x = d3.y; /*this is okay*/
D2 d2; d2.x = d2.y; /* this is not okay! */
}
//Explanation: D3 is using B even though protected inheritance means that x and y can only be accessed by the derived class and its descendants; neither of whom are D3.
//Another note: D3 cannot be created because D3 has not yet been defined but that's a nitpick.
class D4: protected D2 {D4 d4; d4.x=d4.y //this is okay
D2 d2; d2.x = d2.y; //not okay! Since we're dealing with x and y D4 can only use itself as B but it cannot use D2 as a B.
}
D2 d2;
d2.x = d2.y; /*This does NOT WORK, results in compile time failure */
3. Private inheritance.
• Private inheritance means that the inheritance can only be accessed by the derived class itself and no other scope.
Summation: http://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance
The same happens with public, private and protected inheritance. Let's consider a class Base and a class Child that inherits from Base.
• If the inheritance is public, everything that is aware of Base and Child is also aware that Child inherits from Base.
• If the inheritance is protected, only Child, and its children, are aware that they inherit from Base.
• If the inheritance is private, no one other than Child is aware of the inheritance.
FOR MORE INFO READ THIS ITS GOOD: http://www.programiz.com/cpp-programming/public-protected-private-inheritance
IMPORTANT!!!!! REMEMBER that calling the function from outside it's scope (For example, from main), is NOT CONSIDERED CALLING IT FROM IT OR ONE OF ITS DESCENDANTS!
____________________
Why use inheritance?
//Before inheritance:
void update_all_objects(std::vector<Object> &v)
{
for(auto& elem: v)
elem.update();
}
//After inheritance:
void update_all_objects(std::vector<Object *> &v)
{
for(auto& elem: v)
elem->update();
}
void update_all_objects(std::vector<Object> &v)
{
for(auto& elem: v)
elem.update();
}
//After inheritance:
void update_all_objects(std::vector<Object *> &v)
{
for(auto& elem: v)
elem->update();
}
--------------------
Base class is the first to be constructed and the last to be destructed.
Also, the objects are only constructed after the stuff inside it is constructed.
Destruction is reverse of Construction.
--------------------
You only need to declare a copy constructor, copy assignment or destructor once there's dynamically allocated content for that class.
Y(const Y& rhs)
:X(rhs),obj4(rhs)
//do NOT do X(rhs.X) or X(rhs::X)
{
}
:X(rhs),obj4(rhs)
//do NOT do X(rhs.X) or X(rhs::X)
{
}
--------------------
Dirty trick to turn protected into public:
public:
using GrandParent::onlyformychildren;
using GrandParent::onlyformychildren;
You can also use it to turn public into private, etc.
Whatever is public/protected in a base class can be made public/protected/private in a derived class.
--------------------
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).
Name lookup is done by going through the scopes and finding the first one.
Overriding only has to do with virtual functions, it hides virtual functions.
A virtual function can be both overriden and hidden, normal functions can only be hidden.
--------------------
Copy Constructor
- Synthesized copy constructor
- The synthesized copy constructor will call the correct base class's copy constructor
Question of whether derived class has dynamically allocated memory is very important.
When doing rule of three REMEMBER to work on the base class of the derived as well. Be sure to do it after doing a temp copy for exception safety!
//Copy Assignment Example
D2& operator=(D2 const & rhs)
{
std::cout << "D2& operator=(D2 const & rhs);\n";
char *tmp_buf = new char[rhs.mLen];
std::copy(rhs.buf, rhs.buf+rhs.mLen, tmp_buf);
B::operator=(rhs);
mLen = rhs.mLen;
delete [] buf;
buf = tmp_buf;
return *this;
}
D2& operator=(D2 const & rhs)
{
std::cout << "D2& operator=(D2 const & rhs);\n";
char *tmp_buf = new char[rhs.mLen];
std::copy(rhs.buf, rhs.buf+rhs.mLen, tmp_buf);
B::operator=(rhs);
mLen = rhs.mLen;
delete [] buf;
buf = tmp_buf;
return *this;
}
--------------------
If you inherit a derived from a base it's a good idea to have the destructor in the base be virtual.
--------------------
What makes a member function different from a normal function?
It has a this pointer as the first argument.
--------------------
class X
{
int foo();
}
{
int foo();
}
Default constructor, copy assignment, copy constructor and destructor are all synthesized.
--------------------
Vptr is the first element in the class. It is a pointer to a virtual table (vtable) of function pointers. &foo will be &B::foo if foo is not overriden, otherwise it will be &C::foo;
Virtual destructors are always overriden. Why? From Stack Overflow:
Here, you'll notice that I didn't declare Base's destructor to be virtual. Now, let's have a look at the following snippet:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
Since Base's destructor is not virtual and b is a Base* pointing to a Derived object, delete b has undefined behaviour. In most implementations, the call to the destructor will be resolved like any non-virtual code, meaning that the destructor of the base class will be called but not the one of the derived class, resulting in a resources leak.
To sum up, always make base classes' destructors virtual when they're meant to be manipulated polymorphically.
If you expect your derived class to use your function polymorphically, you must virtualize the destructor.
--------------------
B* bptr = new D2(50,50);
delete bptr;
delete bptr;
B();
D2(50,50);
~D2();
~B();
--------------------
struct B {};
struct D1: B {virtual void foo();};
struct D2: D1 {void foo();};
int main()
{
D1 d1;
d1.foo(); /* foo is a virtual fn.
So is foo called up by looking up the
virtual table? */
//Answer is NO. Because it's not a reference or pointer - Polymorphism cannot be used.
D1& rd1 = d1;
rd1.foo(); //Using polymorphism.
}
struct D1: B {virtual void foo();};
struct D2: D1 {void foo();};
int main()
{
D1 d1;
d1.foo(); /* foo is a virtual fn.
So is foo called up by looking up the
virtual table? */
//Answer is NO. Because it's not a reference or pointer - Polymorphism cannot be used.
D1& rd1 = d1;
rd1.foo(); //Using polymorphism.
}
--------------------
Default arguments are determined at compile time.
--------------------
Top Level vs Low Level consts
TOP-LEVEL CONST IS CONST ON THE SURFACE in C++ you cannot assign a low-level const to a non const BUT you can assign a top level const to a non const
--------------------
Upcasting vs Downcasting
Downcasting is casting from base to derived
Upcasting is casting from derived to base.
--------------------
Dynamic_cast
DYNAMIC_CAST FAILS FOR DOWNCAST
- Downcast for reference types
→ During run-time, if the downcast fails, Exception std::bad_cast is thrown.
- Downcast for pointer types
→ During runtime, if the downcast fails, nullptr is returned.
- Cast into a void * (C++ standard), void * ptr = dynamic_cast<void*>((B*));
→ Cast into the pointer of the most derived type.
-dynamic_cast can only downcast polymorphic types, so sayeth the Standard. (Criteria: MUST have a virtual function.)
- UPCAST DOESNT NEED STATIC_CAST OR DYNAMIC_CAST OR ANY CAST
B b;
D d;
b = d; //this is okay
B& rb = d;
const B& rb = d;// okay
B b1,b2;
B b3(b1);
B::B(const B&);
B& B::operator = (const B&); //this is why you can do theabove!
//dynamic_cast does not concern itself with upcasts, but mainly with downcasts.
B* bptr;
D * dptr;
dptr = static_cast<D*>(bptr); //can COMPILE but isn't safe, only use static cast if youre 100% sure bptr is a D object.
//dynamic_cast only works with classes with virtual ptr.
D d;
b = d; //this is okay
B& rb = d;
const B& rb = d;// okay
B b1,b2;
B b3(b1);
B::B(const B&);
B& B::operator = (const B&); //this is why you can do theabove!
//dynamic_cast does not concern itself with upcasts, but mainly with downcasts.
B* bptr;
D * dptr;
dptr = static_cast<D*>(bptr); //can COMPILE but isn't safe, only use static cast if youre 100% sure bptr is a D object.
//dynamic_cast only works with classes with virtual ptr.
--------------------
Virtual copy constructors are not allowed.
--------------------
Covariant return types
--------------------
VIRTUAL FUNCTIONS DO NOT RESPECT ACCESS SPECIFIERS IF AT RUN-TIME