描述这类的文章有很多,这里用最简洁的方式用于记牢:
1、什么是重载(overload):
在同一个作用域下的两个同名函数,并且它们的参数不同(返回值是否相同可选),这样的两个函数叫重载。
注意理解"在同一个作用域下",至少包括以下:
a. 在同一个名字空间下面,比如都在名字空间abcde下定义的两个普通函数,或者两个全局函数;
b. 在同一个类型下的同一个作用域下,比如都在class A下的public/protected/private;
重载是"静态联编(静态绑定)"的一种。
插:所谓的"联编(或者叫绑定)",是指"将一个标识符号,和一个存储地址关联起来"。"联编"分为两种,一种是"静态联编",指的是编译时即确定关联关系,包括:普通函数重载、类成员函数重载、运算符重载、模板重载、隐藏(没有修饰以virtual的父子类同名成员函数);一种是"动态重载",指的是运行时确定调用哪个,包括:覆盖。"静态联编"和"动态联编"共同组成了c++的"多态"功能。
如下面的两个全局函数:
double overload_sample_0 () {
std::cout << "overload sample 0_1 in namespace what_is_overload" << std::endl;
return 1.1;
}
int overload_sample_0 (int a) {
std::cout << "overload sample 0_2 in namespace what_is_overload" << std::endl;
return 1;
}
两个函数的名称都是overload_sample_0,作用域都是全局作用域,参数不一样,重载;
再如下面的两个函数:
namespace what_is_overload {
void overload_sample_1 () {
std::cout << "overload sample 1_1 in namespace what_is_overload" << std::endl;
}
int overload_sample_1 (int a) {
std::cout << "overload sample 1_2 in namespace what_is_overload" << std::endl;
return 0;
}
};
两个函数的名称都是overload_sample_1,作用域都是名字空间what_is_overload之下,参数不一样,重载;
再如下面的函数:
class cls {
public:
cls() = default;
~cls() = default;
cls(const cls &other) = default;
cls(cls &&other) = default;
void overload_1 (){ std::cout << "overload 1" << std::endl; }
void overload_1 (int a){ std::cout << "overload 1" << std::endl; }
//int overload_1 () { std::cout << "overload 1" << std::endl; return 0; } // could not be distinguished with the first one
int overload_1 (char c) { std::cout << "overload 1" << std::endl; return 0; }
protected:
void overload_2 (){ std::cout << "overload 2" << std::endl; }
void overload_2 (int a){ std::cout << "overload 2" << std::endl; }
//int overload_2 () { std::cout << "overload 2" << std::endl; return 0; } // could not be distinguished with the first one
int overload_2 (char c) { std::cout << "overload 2" << std::endl; return 0; }
private:
void overload_3 (){ std::cout << "overload 3" << std::endl; }
void overload_3 (int a){ std::cout << "overload 3" << std::endl; }
//int overload_3 () { std::cout << "overload 3" << std::endl; return 0; } // could not be distinguished with the first one
int overload_3 (char c) { std::cout << "overload 3" << std::endl; return 0; }
};
public、protected、private下,都有各自内部的类成员函数的,函数名相同,作用域相同,参数不一样,重载;
插:描述重载时,很多文章注重说,函数的名称一样,参数、返回值不同,事实上精确的是 ,函数的参数必须不同。试想下面两个函数:
int overload(){.....}
double overload() {.....}
然后调用时:overload(); //which one?
函数的参数一样,编译器怎么可能区分调用时调用的是哪一个。
2、什么是覆盖(override):
父子类中的同名、同参数、同返回值的多个成员函数,从子到父形成的关系称为覆盖关系。
覆盖关系属于"动态联编",即运行时通过确定指针所指向的是哪个对象,决定运行哪个实现,支撑"动态联编"的是虚函数表,关于虚函数表:
a. 什么是虚函数表:对含有"virtual"修饰符的类,编译器在编译时,会给该类型制造一个该类型所属的虚函数表;
b. 虚函数表本质上是:一个函数指针表,类的函数名-----地址,这个函数必须用virtual修饰过;
c. 虚函数表在哪:在应用程序的常量区;
d. 虚函数表怎么作用:运行时,根据指针所对应的对象是哪个类型的对象,从虚函数表中找到对应函数名的地址进而执行;
对析构函数用virtual修饰是典型的覆盖的运用,当(父类指针指向的)子类对象发生析构时:
a. 如果父类的析构函数用virtual修饰,那么析构时,通过"动态联编"会调用子类的析构函数;然后自然析构父类;
b. 如果父类的析构函数没有用virtual修饰,那么析构时,仅仅自然析构父类;这个过程实际就是所谓的隐藏;
参考下面的代码:
//what is inherit? inherit is that father and son has the same function name and the same arg and same return-value
//in c++11, if do want to override, you'd better explicit notice the son function with "override", so compiler will do know that it is inherit, but not another base-virtual-function.
class father {
public:
father() = default;
//~father() { std::cout << "father destruct" << std::endl; };
~father() { std::cout << "father destruct" << std::endl; };
father(const father &other) = default;
father(father &&other) = default;
virtual void inherit () { std::cout << "inherit, father" << std::endl; }
};
class son : public father {
public:
son() = default;
//~son() { std::cout << "son destruct" << std::endl; };
virtual~son() { std::cout << "son destruct" << std::endl; };
son(const son &other) = default;
son(son &&other) = default;
//explicit notice compiler that, inherit_c11 here is override of function inherit
//if qualify with "final", class grandson could not override.
//"override" and "final" is used in c++11
virtual void inherit () override { std::cout << "inherit, son" << std::endl; }
//virtual void inherit () override final { std::cout << "inherit, son" << std::endl; }
};
class grandson : public son {
public:
grandson() = default;
virtual ~grandson() { std::cout << "grandson destruct" << std::endl; };
grandson(const grandson &other) = default;
grandson(grandson &&other) = default;
virtual void inherit () override final {std::cout << "inherit, grandson" << std::endl; }
};
父类、子类、孙类,相继是public的继承关系,析构函数均用virtual修饰,下面依次是实验的方式的知识点的巩固:
a. 如果析构函数去除了virtual修饰,那么尝试下面的测试,看看差别在哪:
son s
仅仅在栈中定义了son类型的变量s,析构时,可以发现父类也会被析构;
std::shared_ptr p((father *)new son());
用father指针,指向在堆中创建的son对象,析构时,可以发现仅仅析构了父类对象,即发生内存泄漏;隐藏;
std::shared_ptr pfs((son *)new father());
用son指针,指向在堆中创建的father对象,这时程序会崩溃;如果加virtual修饰,会正常析构父类对象;
结论:
1、父子类的析构函数,要加入virtual修饰;
2、不要用子类指针,指向父对象;
用父类指针指向子类对象,实际开发中是具备实际意义的,是动态实现多态的使用呈现。
而反过来是令人奇怪的,因为子类已经继承了父类的方法,为什么要用子类指针指向父类对象。
对于普通函数的覆盖,和析构函数是一样道理。注意:
1、构造函数不可以进行覆盖;
2、运算符重载属于重载,不可以进行覆盖;
3、要保证覆盖的函数,函数参数、返回值均相同;
一定要加入virtual修饰符,但有时可能会忘记,如果忘记则可能会出现隐藏:
1、父类有成员函数function;没有用virtual修饰;
2、子类有成员函数function,函数名、参数、 返回值均相同;
那么如下面的例子,会出现问题:
std::shared_ptr psf((father *)new son());
这时如果执行psf->function(),不会在运行时"动态联编",而是出现如下情况:那么指针是什么类型,就执行哪个类型的function;或者说,override退化成hide;隐藏(hide)一般属于程序开发bug;
为了防止忘写virtual导致的隐藏,c++11规定可以在程序中,对希望是覆盖的成员函数,显式的修饰以override,这样如果没写virtual会在编译时报错,避免忘修饰virtual;
c++11另外还规定了修饰符final,对于不想再被子类覆盖的虚函数,加入final修饰符,它的子类即便想覆盖也无妨再覆盖了,或者说,覆盖行为到它这里截止;
3、什么是隐藏(hide):
知道了什么是覆盖,那么忘记修饰virtual导致的"覆盖失误"就是隐藏。隐藏的特点是:不管指针实际指向的对象是什么样的类型,完全根据指针类型,执行对应的成员函数。
隐藏导致编译器完全没有创建虚函数表(因为没有virtual修饰符),所以完全走的是"静态联编",即按指针类型来。隐藏的危害发生在,原本希望是覆盖,结果发现和预期完全不符。
下面是隐藏的一些例子:
TEST (test1, what_is_hide) {
//hide: father class and son class, has the function with same name, but without qualify with 'virtual', type of pointer would determine which would be call
class father {
public:
father() = default;
~father() = default;
father(const father &other) = default;
father(father &&other) = default;
//for hide, whether return-value is also same, is not necessary.so hide_0 equals hide_1
void hide_0 () { std::cout << "hide_0, father" << std::endl; }
void hide_1 () { std::cout << "hide_1, father" << std::endl; }
void hide_2 (int a) { std::cout << "hide_2, father" << std::endl; }
};
class son : public father {
public:
son() = default;
~son() = default;
son(const son &other) = default;
son(son &&other) = default;
void hide_0 () { std::cout << "hide_0, son" << std::endl; }
int hide_1 () { std::cout << "hide_1, son" << std::endl; }
void hide_2 () { std::cout << "hide_2, son" << std::endl; }
};
//when hide, type of pointer determine which would be call
//hide explains that why destruct-function without virtual, would memory leak. destruct-function without virtual, would determined by type of pointer, but not type of object
father f;
son s;
f.hide_0(); //father
f.hide_1(); //father
f.hide_2(1);//father
s.hide_0(); //son
s.hide_1(); //son
s.hide_2(); //son
std::shared_ptr ps(new son());
ps->hide_0(); //son
ps->hide_1(); //son
ps->hide_2(); //son
std::shared_ptr psf((son *)new father());
psf->hide_0(); //son
psf->hide_1(); //son
psf->hide_2(); //son
std::shared_ptr pf(new son());
pf->hide_0(); //father
pf->hide_1(); //father
pf->hide_2(1); //father
std::shared_ptr pfs((father *)new son());
pfs->hide_0(); //father
pfs->hide_1(); //father
pfs->hide_2(2); //father
}
c++多态的总结:
1、"静态联编":
1、同一作用域(相同名字空间,类型下相同作用域(public/protected/private))的普通/成员函数,函数名相同,参数不相同;
2、运算符重载;
3、编译时确定模板实现类(模板重载);
如:template
4、隐藏;隐藏是程序开发的bug应该加以避免;
2、"动态联编":
指的就是覆盖,运行时由虚函数表根据所处对象的类型,确定执行哪一个函数;
良好的程序应该使用覆盖;
3、如何避免覆盖变成隐藏:
a. 不要忘了用virtual修饰;
b. 同时使用override修饰,忘了用virtual时编译器可以报错;