Polymorphism and type conversion in C + +

Posted by Aldark on Wed, 09 Feb 2022 00:57:28 +0100

catalogue

Introduction of polymorphism

Polymorphic restriction

Type conversion

Implicit conversion

Explicit conversion

Introduction of polymorphism

Let's take a look at the following examples. It is instinctive for humans to eat with their hands. The British eat with knives and forks, while we eat with chopsticks. When we ask this person how to eat, we should answer according to his country, rather than simply say "eat with your hands"

#include <iostream>
#include <string.h>
#include <unistd.h>

using namespace std;

class Human {
public:
	void eating(void) { cout<<"use hand to eat"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
};

void test_eating(Human& h)
{
	h.eating();
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	h.eating();
	e.eating();
	c.eating();
	test_eating(h);
	test_eating(e);
	test_eating(c);

	return 0;
}

The execution results are as follows. It can be seen that at the beginning, it is implemented by overwriting. Later, it can not automatically identify which country it is through function reference. Instinctively, it is eaten by hand. The mechanism inside is static binding, which will be explained later. Therefore, we need to realize automatic recognition by polymorphism

use hand to eat
use knife to eat
use chopsticks to eat
use hand to eat
use hand to eat
use hand to eat

How to implement it? Add a virtual function to the base class and add virtual before the eating function. The modification procedure is as follows. The derived class inherits the eating function of the base class, so it also becomes a virtual function. Virtual can not be written for the eating function of the derived class

...

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
};

...

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_eating(h);
	test_eating(e);
	test_eating(c);

	cout<<"sizeof(Human) = "<<sizeof(h)<<endl;
	cout<<"sizeof(Englishman) = "<<sizeof(e)<<endl;
	cout<<"sizeof(Chinese) = "<<sizeof(c)<<endl;

	return 0;
}

The implementation results are as follows. It can be seen that the eating methods of various countries are automatically recognized, rather than simply eating by hand. In the above procedures, the mechanism is the so-called dynamic linkage

use hand to eat
use knife to eat
use chopsticks to eat
sizeof(Human) = 16
sizeof(Englishman) = 16
sizeof(Chinese) = 16

For non virtual functions, static binding is adopted, and which function is called is determined during compilation. Therefore, it can not automatically identify which Chinese, but simply eat by hand; For virtual functions, dynamic binding is adopted. The object with virtual functions contains a pointer, as shown in the figure below, pointing to the virtual function table. When calling the virtual function table, the table will be found according to the pointer in the object, and the function will be taken out from the table for execution. For static binding, the efficiency is high, while dynamic binding supports polymorphism. You can see the 4-byte variable size of the above original object, The virtual function table is executed with an extra 12 byte pointer

 

 

Polymorphic restriction

1. Using pointers or references to use objects will have polymorphism. The modification procedure is as follows

...

void test_eating(Human h)
{
	h.eating();
}

...

The execution results are as follows: for test_ There is no pointer when passing values in the eating function, so it belongs to static binding, such as "test_eating(e);", e will first become a human class. There is a (base class) and pointer in e. when it is converted to human type, only the base class is left. Therefore, it can only be static binding, and what can only be called is human's eating function

use hand to eat
use hand to eat
use hand to eat
sizeof(Human) = 16
sizeof(Englishman) = 16
sizeof(Chinese) = 16

2. Only member functions of a class can be declared as virtual functions

3. Static member functions cannot be virtual functions

4. Inline functions cannot be virtual functions

5. Constructors cannot be virtual functions

6. Destructors are generally declared as virtual functions, and the modification procedure is as follows

#include <iostream>
#include <string.h>
#include <unistd.h>

using namespace std;

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
};

void test_eating(Human h)
{
	h.eating();
}

int main(int argc, char **argv)
{
	Human* h = new Human;
	Englishman* e = new Englishman;
	Chinese* c = new Chinese;

	Human *p[3] = {h, e, c};
	int i;

	for (i = 0; i < 3; i++)
	{
		p[i]->eating(); 
		delete p[i];    
	}
	return 0;
}

The execution results are as follows: if the destructor in the base class is not set to virtual, in the main function, all three call the destructor of the base class instead of their respective destructors; The destructor in the base class Human is set to virtual. For Englishman, it will call its destructor ~ Englishman() first, and then call the destructor ~ Human() of the base class

use hand to eat
~Human()
use knife to eat
~Englishman()
~Human()
use chopsticks to eat
~Chinese()
~Human()

7. Overload cannot be set as virtual function. For override: function parameters and return values are the same, it can be set as virtual function

8. Exception of return value: if the function parameters are the same, but the return value is the pointer or reference of the current object, it can also be set as a virtual function

For the following program, the return value is not a pointer or reference to the object, so this compilation will report an error

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
	virtual void test(void) {cout<<"Human's test"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
	virtual int test(void) {cout<<"Englishman's test"<<endl; return 1; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
	virtual int test(void) {cout<<"Chinese's test"<<endl; return 1; }
};

Modify program

...

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
	virtual Human* test(void) {cout<<"Human's test"<<endl; return this; } 
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
	virtual Englishman* test(void) {cout<<"Englishman's test"<<endl; return this; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
	virtual Chinese* test(void) {cout<<"Chinese's test"<<endl; return this; }
};

void test_return(Human& h)
{
	h.test();
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_return(h);
	test_return(e);
	test_return(c);


	return 0;
}

The compilation will not report errors, and the execution results are as follows

Human's test
Englishman's test
Chinese's test
~Chinese()
~Human()
~Englishman()
~Human()
~Human()

 

Type conversion

There are two types, one is implicit conversion, the other is explicit conversion

 

Implicit conversion

The following C code, where "int i = d", double is implicitly converted to int, "int *p = str", char * is implicitly converted to int *

	double d = 100.1;
	int i = d;
	char *str = "xiaoma";
	int *p = str;

Explicit conversion

Where "int *p = (int *)str", char * is explicitly converted to int *, and the print statement will be warned in the 64 bit system. The pointer is 8 bytes, while the unsigned int type is 4 bytes

	char *str = "xiaoma";
	int *p = (int *)str;

	printf("i = %d, str = 0x%x, p = 0x%x\n", i, (unsigned int)str, (unsigned int)p); 

Use the following four functions for conversion in C + + (template function)

reinterpret_cast

Format: reinterpret_ cast<type-id>(expression)

It is equivalent to C-style forced type conversion with parentheses "(type ID)"

  • Type ID must be a pointer, reference, arithmetic type, function pointer or member pointer
  • It can convert a pointer to an integer or an integer to a pointer
  • Similar to C-style coercion, there is no security check
	char *str = "xiaoma";
	int *p = reinterpret_cast<int *>(str);

const_cast

Format: const_ cast<type_ id>(expression)

This operator is used to remove const or volatile attributes of the original type. In addition to const or volatile modifiers, type_ The types of ID and expression are the same

Look at the following code, reinterpret_cast cannot directly cast str type, and cannot remove const attribute. Therefore, cast_ Cast first removes the const attribute and then uses reinterpret_cast cast to int * type

	const char *str = "100ask.taobao.com";  
	char *str2 = const_cast<char *>(str);   
	int *p = reinterpret_cast<int *>(str2);

dynamic

Format: dynamic_ cast<type-id>(expression)

This operator converts expression into an object of type ID. type is must be a pointer to a class, a reference to a class, or void *; If type ID is a class pointer type, expression must also be a pointer; If type ID is a reference, expression must also be a reference.

  • Used in polymorphic situations, that is, there must be virtual functions
  • It is mainly used for up conversion and down conversion between class levels, and can also be used for cross conversion between classes
  • When performing uplink conversion between class hierarchies, dynamic_cast and static_ The effect of cast is the same; During downlink conversion, dynamic_cast has the function of type checking, which is better than static_cast is safer.

In the case of polymorphism, I want to test_ In the eating function, we can distinguish whether the person is British or Chinese

void test_eating(Human& h)
{
	Englishman *pe;
	Chinese    *pc;
	
	h.eating();

	/* Want to tell whether this "person" is British or Chinese? */
	if (pe = dynamic_cast<Englishman *>(&h)) 
		cout<<"This human is Englishman"<<endl;

	if (pc = dynamic_cast<Chinese *>(&h))
		cout<<"This human is Chinese"<<endl;
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_eating(h);
	test_eating(e);
	test_eating(c);
	return 0;
}

The execution results are as follows. For dynamic type conversion, the virtual function table is always found through the pointer in the object. The virtual function table contains not only virtual function information, but also inheritance information. It is judged according to the inheritance information

use hand to eat
use knife to eat
This human is Englishman
use chopsticks to eat
This human is Chinese
~Chinese()
~Human()
~Englishman()
~Human()
~Human()

Guangxi people are derived from Chinese people. The modification procedure is as follows

class Guangximan : public Chinese {
public:
	void eating(void) { cout<<"use chopsticks to eat, I come from guangxi"<<endl; }
};

void test_eating(Human& h)
{
	Guangximan *pg;
	
	h.eating();

	/* Want to tell whether this "person" is British or Chinese? */ 
	if (pe = dynamic_cast<Englishman *>(&h))
		cout<<"This human is Englishman"<<endl;

	if (pc = dynamic_cast<Chinese *>(&h)) 
		cout<<"This human is Chinese"<<endl;
	
	if (pg = dynamic_cast<Guangximan *>(&h))
		cout<<"This human is Guangximan"<<endl;
	
}

int main(int argc, char **argv)
{
	Guangximan g;

	test_eating(g); //Guangxi people or Chinese people, in the class information also contains those inheritance information
	return 0;
}

The execution results are as follows. Because the virtual function table contains inheritance information, it can be converted into Chinese and Guangxi

use chopsticks to eat, I come from guangxi
This human is Chinese
This human is Guangximan
~Chinese()
~Human()

Change the pointer to a reference and modify the program as follows. If a person from Guangxi is converted to an Englishman, there will be an exception. The conversion is unsuccessful and the program directly crashes. Therefore, the program crashes in "English & PE = dynamic_cast < English & > (H)", in which the base class is converted to a derived class to realize the downstream conversion. For the dynamic conversion of references, the conversion process may be successful, It is also possible to fail, which is safer than cast

void test_eating(Human& h)
{
	Englishman& pe = dynamic_cast<Englishman&>(h); 
	Chinese&    pc = dynamic_cast<Chinese&>(h);    //Downlink conversion   
	Guangximan& pg = dynamic_cast<Guangximan&>(h);   
	h.eating();
}

int main(int argc, char **argv)
{
	Guangximan g;

	test_eating(g);

	return 0;
}

If it is modified to cast, there will be a problem for Guangxi people to cast to British people. If the British operation is involved, there will be a problem, but the compilation can pass, so it is not safe

void test_eating(Human& h)
{
	Englishman& pe = reinterpret_cast<Englishman&>(h); 
	Chinese&	pc = reinterpret_cast<Chinese&>(h); 
	Guangximan& pg = reinterpret_cast<Guangximan&>(h);

	h.eating();
}

static_cast

Format: static_ cast<type-id>(expression)

This operator converts expression to type ID, but there is no runtime type check to ensure the security of the conversion.

  • Used to convert pointers or references between base classes and subclasses in the class hierarchy.
  • It is safe to perform uplink conversion (convert the pointer or reference of the subclass into the representation of the base class);
  • When performing downstream conversion (converting a base class pointer or reference to a subclass pointer or reference), it is unsafe because there is no dynamic type check.
  • It is used for the conversion between basic data types, such as converting int to char and int to enum: the security of this conversion should also be guaranteed by developers.
  • Convert void pointer to pointer of target type (unsafe!!)
  • Convert any type of expression to void type.

Note: static_cast cannot convert const, volatile, or__ unaligned attribute.

Modify the main function for static_ For cast, the uplink conversion is safe, but the downlink conversion cannot be checked out, which has potential safety hazards

int main(int argc, char **argv)
{
	Human h;
	Guangximan g;
	Englishman *pe;

	pe = static_cast<Englishman *>(&h); //Downlink conversion, but Human is not necessarily British. It is unsafe and has potential safety hazards. static_cast can't check it out

	//Englishman *pe2 = static_ cast<Englishman *>(&g);  For uplink conversion, the compiler will check for errors. Guangxi people cannot convert to British people

	Chinese *pc = static_cast<Chinese *>(&g);  //Uplink conversion is safe

	return 0;
}