Lecture 7 Classes Inheritance and Type Compatibility

You might also like

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 28

Classes, inheritance and type

compatibility
Classes and Data Types
• Each new class constitutes a new type of data.
• Each object constructed on the basis of such a class is like a value of
the new type.
Object Compatibility
• Any two objects may (or may not) be compatible in the sense of their
types.
Example: Cat and Dog Classes
#include <iostream> int main() {
Cat *a_cat = new Cat();
using namespace std; Dog *a_dog = new Dog();

class Cat { a_cat->make_sound();


public: a_dog->make_sound();
void make_sound() {
cout << "Meow! Meow!" << endl; // Incompatible assignments (will cause compiler errors)
// a_dog = a_cat;
}
// a_cat = a_dog;
};
class Dog {
return 0;
public:
}
void make_sound() {
• The Cat class and the Dog class have identical structures but
cout << "Woof! Woof!" << endl; represent different data types.
} • Assigning a Dog object to a Cat pointer (and vice versa) is not
}; allowed.
Type Compatibility: More
Complex Case
Introducing the Pet Class
#include <iostream> • The Pet class is a base class.
using namespace std; • It has a protected member
variable name to store the pet's
class Pet {
protected: name.
string name; • It has a public constructor to
public:
Pet(string n) { initialize the name.
name = n; • It has a public method run() that
}
void run() { prints a message with the pet's
cout << name << ": I'm running" << endl; name.
}
};
Dog - A Child of Pet
class Dog : public Pet { • The Dog class inherits from the
public: Pet class.
• It inherits the name member
Dog(string n) : Pet(n) {}
variable and the run() method
void make_sound() { from the Pet class.
cout << name << ": Woof! • It has a public constructor to
Woof!" << endl; initialize the inherited name.
} • It has a public method
}; make_sound() that prints a
barking sound with the dog's
name.
Cat - Another Child of Pet
class Cat : public Pet { • The Cat class inherits from the Pet
public: class.
• It inherits the name member
Cat(string n) : Pet(n) {}
variable and the run() method
void make_sound() { from the Pet class.
cout << name << ": Meow! • It has a public constructor to
Meow!" << endl; initialize the inherited name.
} • It has a public method
}; make_sound() that prints a
meowing sound with the cat's
name.
Inheritance and Compatibility
int main() { • An object of a subclass (e.g., Dog or
Pet a_pet("pet"); Cat) can be assigned to a variable
Cat a_cat("Tom"); of the superclass (e.g., Pet).
Dog a_dog("Spike"); • This is because the subclass object
inherits all the capabilities of the
a_pet.run(); superclass object.
a_dog.run(); • You cannot assign a superclass
a_dog.make_sound(); object to a variable of the subclass.
a_cat.run(); • The
Ouputsuperclass object may not have
a_cat.make_sound(); all theI'm
pet: capabilities
running of the subclass
Spike: I'm running
}
object.
Spike: Woof! Woof!
Tom: I'm running
Tom: Meow! Meow!
Inheritance and Pointers
#include <iostream> • The Pet class is a base class.
using namespace std; • It has a protected member
variable name to store the pet's
class Pet {
protected: name.
string name; • It has a public constructor to
public:
Pet(string n) { initialize the name.
name = n; • It has a public method run() that
}
void run() { prints a message with the pet's
cout << name << ": I'm running" << endl; name.
}
};
Dog - A Child of Pet
class Dog : public Pet { • The Dog class inherits from the
public: Pet class.
• It inherits the name member
Dog(string n) : Pet(n) {}
variable and the run() method
void make_sound() { from the Pet class.
cout << name << ": Woof! • It has a public constructor to
Woof!" << endl; initialize the inherited name.
} • It has a public method
}; make_sound() that prints a
barking sound with the dog's
name.
Cat - Another Child of Pet
class Cat : public Pet { • The Cat class inherits from the Pet
public: class.
• It inherits the name member
Cat(string n) : Pet(n) {}
variable and the run() method
void make_sound() { from the Pet class.
cout << name << ": Meow! • It has a public constructor to
Meow!" << endl; initialize the inherited name.
} • It has a public method
}; make_sound() that prints a
meowing sound with the cat's
name.
Pointers to Superclass Objects
int main() • We can declare pointers of the
{ superclass type (Pet*) and assign them
Pet *a_pet1 = new Cat("Tom"); objects of the subclass types (Cat and
Dog).
Pet *a_pet2 = new Dog("Spike");
• This is because the subclass objects
inherit all the functionalities of the
a_pet1 -> run(); superclass object.
// 'a_pet1 -> make_sound();' is not allowed • However, we cannot call subclass-
here!
specific methods (like make_sound())
a_pet2 -> run(); on these pointers because the
// 'a_pet2 -> make_sound();' is not allowed compiler cannot guarantee that the
here! object pointed to actually has that
} method.
Type Compatibility - Recovering
Lost Functionality
In the previous section, we saw that we cannot call subclass-specific methods on
pointers of the superclass type due to compiler restrictions. This section explores
how to recover this functionality using the static_cast operator.
The Problem with Superclass Pointers
• The compiler restricts calling subclass methods on superclass
pointers.
• This is because the compiler cannot guarantee the object pointed to
has the specific method.
• We know for sure that Dog and Cat objects have make_sound()
methods, but the compiler doesn't.
The Solution: static_cast Operator
• We can use the static_cast operator to inform the compiler about our
intentions.
• static_cast is used for static type conversion during compilation.
• It allows us to cast a pointer from one type to another.
Using static_cast
#include <iostream> class Cat : public Pet {
using namespace std; public:
class Pet { Cat(string n) : Pet(n) {};
protected: void make_sound()
string name; {
public: cout << name << ": Meow! Meow!" << endl;
Pet(string n) }
{ };
name = n; int main()
} {
void run() Pet *a_pet1 = new Cat("Tom");
{ Pet *a_pet2 = new Dog("Spike");
cout << name << ": I'm running" << endl; a_pet1 -> run();
} static_cast<Cat *>(a_pet1) -> make_sound();
}; a_pet2 -> run();
class Dog : public Pet { static_cast<Dog *>(a_pet2) -> make_sound();
public: }
Dog(string n) : Pet(n) {}; • We use static_cast<target_type>(expression) to
void make_sound() convert the expression to the target type.
{
• In this example, we cast a_pet1 to Cat* and a_pet2
cout << name << ": Woof! Woof!" << endl;
to Dog*.
}
}; • This allows us to call make_sound() on these
• static_cast allows us to recover lost functionality due to superclass
pointer limitations.
• It should be used cautiously as it bypasses compiler checks and can
lead to errors if not used correctly.
• we'll discuss the dangers of misusing static_cast operator and explore
a safer alternative - dynamic_cast. We'll see how static_cast can lead
to unexpected behavior and potential program crashes.
The Problem with Static_Cast
• Static_cast performs a type conversion at compile time
• Compiler trusts the programmer to ensure compatibility
• No runtime check for object type
Code Example: Casting Wrong Types
#include <iostream> int main()
using namespace std;
{
class Pet {
protected: Pet *a_pet1 = new Cat("Tom");
string name;
Pet *a_pet2 = new Dog("Spike");
public:
Pet(string n) { name = n; }
void run()
a_pet2 -> run();
{
cout << name << ": I'm running" << endl; static_cast<Cat *>(a_pet2) -> make_sound();
} a_pet1 -> run();
};
class Dog : public Pet { static_cast<Dog *>(a_pet1) -> make_sound();
public: }
Dog(string n) : Pet(n) {};
void make_sound() • We create Pet objects using pointers
{
• Attempt to call Cat's make_sound on Dog pointer (static_cast)
cout << name << ": Woof! Woof!" << endl;
} • Attempt to call Dog's make_sound on Cat pointer (static_cast)
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound()
{
cout << name << ": Meow! Meow!" << endl;
}
};
Unexpected Output
Spike: I'm running
Spike: Meow! Meow!
Tom: I'm running
Tom: Woof! Woof!
• Program successfully compiles but produces incorrect output
• Cat object makes dog sound (Meow!) and vice versa
Why This Happens
• Compiler trusts the programmer with static_cast
• No check for object type compatibility at runtime
A Safer Alternative: Dynamic_Cast
• Dynamic_cast performs type conversion at runtime
• Checks if the object pointed to is actually of the target type
• Returns nullptr if conversion fails (prevents program crash)
Type Compatibility: Upcasting and
Inheritance
• Upcasting refers to using a pointer of a base class to point to a derived
class object. We'll see how this works even with long inheritance
chains.
Upcasting Rule
• Objects at higher levels (base classes) are compatible with objects at
lower levels (derived classes)
• This applies even with complex inheritance hierarchies
Code Example: Upcasting in Action
#include <iostream>
using namespace std;
class Pet {
protected: int main()
string name;
public:
{
Pet(string n) Pet *a_pet;
{
name = n;
Persian *a_persian;
} a_pet = a_persian = new Persian("Mr.
void run() Bigglesworth");
{
cout << name << ": I'm running" << endl;
a_persian -> make_sound();
} static_cast<Persian *>(a_pet) -> make_sound();
};
class Cat : public Pet {
}
public: • We define three classes: Pet, Cat, and Persian
Cat(string n) : Pet(n) {};
void make_sound()
(Inherits from Cat)
{ • Create a Persian object and assign it to both a
cout << name << ": Meow! Meow!" << endl; Persian pointer and a Pet pointer (upcasting)
}
}; • Call make_sound on both pointers
class Persian : public Cat {
public:
Persian(string n) : Cat(n) {};
Program Output
Mr. Bigglesworth: Meow! Meow!
Mr. Bigglesworth: Meow! Meow!
• Upcasting works seamlessly
• Both calls to make_sound produce the same output
Conclusion
• Upcasting is a powerful mechanism in inheritance
• Allows for flexible code design
• Ensures compatibility across inheritance hierarchies

You might also like