PrevUpHome

Chapter 2. More Things: new, delete, this, virtual, polymorphism, and Multiple Inheritance

Memento

This chapter chats some important features that might be ignored in previous CPP Fast Tutorial .

Back Index: Learn CPP

Object Inheritance

Object inheritance is that the child class inherits the features and actions of its parent class, however the child class has its own new features. For example, Animal can move, Bird inherits Animal, but Bird can not only move, but also fly; Crawler inherits Animal, Crawler can not only move, but also walk; And you can say all birds and crawlers are still animals.

#include <iostream>
#include <string>

class Animal {
public:
	void move() {
		std::cout << "I am moving ...\n";
	}
};

class Crawler: public Animal {
private:
	std::string name;
public:
	Crawler(const std::string & aName) {
		name = aName;
		std::cout << "My name is " << name << ", I am a beast.\n";
	}
	void walk() {
		// Child class can use properties and methods from its parent class.
		this->move();

		std::cout << "I am walking ...\n";
	}
};

int main() {
	Crawler crawler1{"Alice"};
	crawler1.walk();
}

Memory Things: new, delete, this, pointer, sizeof

(c++ new, c++ delete, c++ this, c++ pointer, c++ sizeof)

Examples:

pointer

int x = 3;
int * pointer_to_x = &x;
std::cout << * pointer_to_x << std::endl; // 3
std::string str = "Hello World!";
std::string & pointer_to_str = &str;
std::cout << * pointer_to_str << std::endl; // Hello World!
class Swan {
public:
	void greeting() {std::cout << "Hello, I am a swan!" << std::endl;}
};
Swan swan;
Swan * pointer_to_swan = &swan;
pointer_to_swan->greeting(); // Hello, I am a swan!
float x = 3.3;
float * ptr1 = &x;
float * ptr2 = ptr1;    // OK

new and delete

int * ptr1 = new int(13);    // Created by new, and inited with 13
* ptr1 = 17;    // Update its value with its pointer (memory address).
delete ptr1;    // We have to delete pointer created by new.

int * ptr2 = new int;    // Created by new, no initialize value.
delete ptr2;

// new and delete can be used on objects too.
class Swan {public: void greeting() {}};
Swan * swan = new Swan;
swan->greeting();
delete swan;

this pointer

#include <iostream>

class HelloWorld {
private:
	int x;
public:
	HelloWorld(int x) {
		this->x = x;
	}
	void func() {
		this->x += 2;    // Increment it by 2
	}
	void fn() {
		this->func();
	}
	void print() {
		std::cout << "x => " << this->x << std::endl;
	}
};

int main() {
	HelloWorld hello{31};
	hello.fn();
	hello.print(); // 33
}

Never delete this:

delete this; // Bad!

Let's make an incident agreement: never delete this! Some old code is still using delete this, but it is bad.

Virtual Method and Method Override

A. Pure Virtual Abstract Base Class and Method: (no implementation)

Pure Virtual Method or Protocol

The base class does not have to implement some of its methods, which can be called pure virtual method or protocol.

class Animal {
public:
	// This is pure virtual method.
	virtual void move() = 0;
};

A pure virtual abstract base class is a class that contains pure virtual method.

B. Override: (implementation)

The unimplemented pure virtual base method must be implemented in its derived class, with override keyword using.

class Crawler: public Animal {
public:
	// Using override to override
	void move() override {
		std::cout << "I am walking ..." << std::endl;
	}
};

The advantage of using override keyword is that it indicates it is a virtual override, and more important it helps you to correct errors (might be a miss typo).

C. Full Example

Code: simple-override.cpp

#include <iostream>

class Animal {
public:
	virtual void move() = 0;
};

class Crawler: public Animal {
public:
	void move() override {
		std::cout << "I am walking ..." << std::endl;
	}
};

class Bird: public Animal {
public:
	void move() override {
		std::cout << "I am flying ..." << std::endl;
	}
};

int main() {
	Crawler alice;
	alice.move();

	Bird bob;
	bob.move();
}

Compile and run the code:

I am walking ...
I am flying ...

Virtual Destructor and Polymorphism

A. Polymorphism

Let's see the code before talking what is polymorphism:

Code: simple-polymorphism.cpp

#include <iostream>

class Animal {
public:
	virtual void move() = 0;
};
class Bird: public Animal {
public:
	void move() override {std::cout << "I am flying ..." << std::endl;}
};
class Crawler: public Animal {
public:
	void move() override {std::cout << "I am walking ..." << std::endl;}
};

int main() {
	Animal * which = nullptr;

	which = new Bird;
	which->move();
	delete which;

	which = new Crawler;
	which->move();
	delete which;
}

Compile and run the code:

I am flying ...
I am walking ...
Quick Explain

B. Problems Above

Do you notice the compiling warnings of compiling the above example?

It might be:

warning: delete called on 'Animal' that is abstract but has non-virtual destructor

It probably said the code has no "virtual destructor".

We can track the creating and destroying of the object, by putting some prints into the constructor and destructor:

Code: track-polymorphism.cpp

#include <iostream>

class Animal {
public:
	virtual void move() = 0;
	Animal() {std::cout <<"create-animal" << std::endl;}
	~Animal() {std::cout << "destroy-animal" << std::endl;}
};
class Bird: public Animal {
public:
	void move() override {std::cout << "I am flying ..." << std::endl;}
	Bird() {std::cout << "create-bird" << std::endl;}
	~Bird() {std::cout << "destroy-bird" << std::endl;}
};
class Crawler: public Animal {
public:
	void move() override {std::cout << "I am walking ..." << std::endl;}
	Crawler() {std::cout << "create-crawler" << std::endl;}
	~Crawler() {std::cout << "destroy-crawler" << std::endl;}
};

int main() {
	Animal * which = nullptr;

	which = new Bird;
	which->move();
	delete which;

	which = new Crawler;
	which->move();
	delete which;
}

Compile and run the code again:

create-animal
create-bird
I am flying ...
destroy-animal
create-animal
create-crawler
I am walking ...
destroy-animal

Do you notice the problems now?

Why?

Simple and Short Answer:

delete which;

Yes, the program only knows which type is the base class pointer type, so delete which will call base class destructor.

But why using which->move() does not cause problem? Because the move method has been marked virtual, it is a virtual abstract base class method.

Can we use virtual on the destructor too? Yes! That's the solution!

C. Virtual Destructor

Add virtual to the destructor and recompile the code, only the base class destructor needs to mark virtual.

Code: polymorphism-virtual.cpp

#include <iostream>

class Animal {
public:
	virtual void move() = 0;
	Animal() {std::cout <<"create-animal" << std::endl;}
	virtual ~Animal() {std::cout << "destroy-animal" << std::endl;}
};
class Bird: public Animal {
public:
	void move() override {std::cout << "I am flying ..." << std::endl;}
	Bird() {std::cout << "create-bird" << std::endl;}
	~Bird() {std::cout << "destroy-bird" << std::endl;}
};
class Crawler: public Animal {
public:
	void move() override {std::cout << "I am walking ..." << std::endl;}
	Crawler() {std::cout << "create-crawler" << std::endl;}
	~Crawler() {std::cout << "destroy-crawler" << std::endl;}
};

int main() {
	Animal * which = nullptr;

	which = new Bird;
	which->move();
	delete which;

	which = new Crawler;
	which->move();
	delete which;
}

Compile and run the code now:

create-animal
create-bird
I am flying ...
destroy-bird
destroy-animal
create-animal
create-crawler
I am walking ...
destroy-crawler
destroy-animal

We can see the bird is created once and destroyed once, the crawler is created once and destroyed once, the animal is created twice and destroyed twice! Everything is correct as expected.

Virtual Inheritance and Multiple Ineritance

A. Multiple Inheritance

Multiple Inheritance is a class that inherits more than one base class.

#include <iostream>

class Bird {public: void fly() {std::cout << "flying ..." << std::endl;}};
class Crawler {public: void walk() {std::cout << "walking ..." << std::endl;}};

class BirdCrawler: public Bird, public Crawler {};

int main() {
	BirdCrawler bc;
	bc.fly();
	bc.walk();
}

What is BirdCrawler? It might be Dinosaur, but it is not really important here, we care cpp multiple inheritance philosophy here only.

A bird-crawler can both fly and walk ...

flying ...
walking ...

B. One Root Class Nested Multiple Inheritance

Can you imagine that? BirdCrawler inherits both Bird and Crawler, Both the Bird and Crawler might inherit the same class: Animal.

Code: nested-inherit.cpp

class Animal {public: virtual ~Animal() {}};

class Bird: public Animal {};

class Crawler: public Animal {};

class BirdCrawler: public Bird, public Crawler {};

int main() {
	BirdCrawler bc;
}

C. Problem 1

Let's track the constructors and destructors:

Code: track-nested.cpp

#include <iostream>

class Animal {
public:
	Animal() {std::cout << "create-Animal\n";}
	virtual ~Animal() {std::cout << "destroy-Animal\n";}
	virtual void move() = 0;
};

class Bird: public Animal {
public:
	Bird() {std::cout << "create-Bird\n";}
	~Bird() override {std::cout << "destroy-Bird\n";}
	void move() override {std::cout << "Bird-move\n";}
	void fly() {std::cout << "Bird-fly\n";}
};

class Crawler: public Animal {
public:
	Crawler() {std::cout << "create-Crawler\n";}
	~Crawler() override {std::cout << "destroy-Crawler\n";}
	void move() override {std::cout << "Crawler-move\n";}
	void walk() {std::cout << "Crawler-walk\n";}
};

class BirdCrawler: public Bird, public Crawler {
public:
	BirdCrawler() {std::cout << "create-BirdCrawler\n";}
	~BirdCrawler() override {std::cout << "destroy-BirdCrawler\n";}
	void move() override {std::cout << "BirdCrawler-move\n";}
};

int main() {
	BirdCrawler bc;
	bc.move();
	bc.fly();
	bc.walk();
}

Compile and run the code:

create-Animal
create-Bird
create-Animal
create-Crawler
create-BirdCrawler
BirdCrawler-move
Bird-fly
Crawler-walk
destroy-BirdCrawler
destroy-Crawler
destroy-Animal
destroy-Bird
destroy-Animal

It seems no problem, isn't it?

But ..., we only tell it to make one object: BirdCrawler bc; Why does Animal is created twice and destroyed twice? Hmm, it might because BirdCrawler inherits from two routine of Animal. But can we just make one copy of their base class Animal?

D. Problem 2

If we change the main:

int main() {
	Animal * animal = new BirdCrawler;
	delete animal;
}

and compile, we will see the error, like:

error: ambiguous conversion from derived class 'BirdCrawler' to base class 'Animal':
	class BirdCrawler -> class Bird -> class Animal
	class BirdCrawler -> class Crawler -> class Animal

Yeah, very exactly, although the class Animal pointer pointing to the derived class BirdCrawler object satisfies virtual destructor polymorphism, however, it has two inheritance routine, so it is ambiguous!

E. Virtual Inheritance

Yeah, both Problem 1 and Problem 2 can be solved by virtual inheritance.

Code: virtual-inheritance.cpp

#include <iostream>

class Animal {
public:
	Animal() {std::cout << "create-Animal\n";}
	virtual ~Animal() {std::cout << "destroy-Animal\n";}
	virtual void move() = 0;
};

// Virtual Inheritance
class Bird: virtual public Animal {
public:
	Bird() {std::cout << "create-Bird\n";}
	~Bird() override {std::cout << "destroy-Bird\n";}
	void move() override {std::cout << "Bird-move\n";}
	void fly() {std::cout << "Bird-fly\n";}
};

// Virtual Inheritance
class Crawler: virtual public Animal {
public:
	Crawler() {std::cout << "create-Crawler\n";}
	~Crawler() override {std::cout << "destroy-Crawler\n";}
	void move() override {std::cout << "Crawler-move\n";}
	void walk() {std::cout << "Crawler-walk\n";}
};

class BirdCrawler: public Bird, public Crawler {
public:
	BirdCrawler() {std::cout << "create-BirdCrawler\n";}
	~BirdCrawler() override {std::cout << "destroy-BirdCrawler\n";}
	void move() override {std::cout << "BirdCrawler-move\n";}
};

int main() {
	Animal * animal = new BirdCrawler;
	delete animal;
}

Compile and run the code now:

create-Animal
create-Bird
create-Crawler
create-BirdCrawler
destroy-BirdCrawler
destroy-Crawler
destroy-Bird
destroy-Animal

We can see all of things are created once and destroyed once.

Only the lowest level of the object inheritance has to be marked virtual .

F. Polymorphism Again

Do you notice that the BirdCrawler object should both fly and walk? We can try to call those two methods with the pointer object:

int main() {
	Animal * animal = new BirdCrawler;
	animal->fly();
	animal->walk();
	delete animal;
}

And try to compile it, but have errors:

error: no member named 'fly' in 'Animal'

error: no member named 'walk' in 'Animal'

Hmm, it said Animal does not have both fly and walk, very exactly!

Because the compiler only knows that animal type is Animal pointer type, but Animal type does not define those two methods.

However, the two methods can be absolutely used by animal, because the pointer object really inherits the gene of both Bird and Crawler, we just need to do more things.

Solution 1: Define those two virtual abstract methods (fly and walk) inside Animal instead of Bird and Crawler. You can try! Very easy. I will not put code here any more.

Solution 2: Using dynamic_cast, Awesome! dynamic_cast is just for polymorphism!

int main() {
	Animal * animal = new BirdCrawler;
	dynamic_cast<Bird *>(animal)->fly(); // It behavies like a bird now.
	dynamic_cast<Crawler *>(animal)->walk(); // It behavies like a crawler now.
	delete animal;
}

PrevUpHome