最近开始重新阅读C++ Primer,不愧是经典书籍,每次翻阅都有新的收获。
今日看书的过程中发现一个过去没有注意的细节,以一篇博文记录,也希望阅读本文的朋友能注意。
第十五章
15.2.4
5.虚函数与默认实参
像其他任何函数一样,虚函数也可以有默认实参。通常,如果有用在给定调用中的默认实参值,该值将在编译时确定。如果一个调用省略了默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数生命中定义的值,如果通过派生类的指针或者引用调用虚函数,则默认实参是在派生类的版本中声明的值。
在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这时就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传给派生类定义的版本,而派生类版本是不同的默认实参定义的。
如果基类virtual函数中的默认实参和派生类中的默认实参不同,则一定会引起错误。原因在于这个值是在编译时确定,而且只与调用函数的类型有关,而和动态类型无关。也就是说,当动态绑定发生的时候,想要使用派生类中的默认实参,是使用的确实基类的!
以一个例子说明这个问题。
//MyClass.h #pragma once class MyClass { public: MyClass(void); ~MyClass(void); virtual void f(int i=2); }; //MyClass.cpp #include "MyClass.h" #include<iostream> using namespace std; MyClass::MyClass(void) { } MyClass::~MyClass(void) { } void MyClass::f(int i) { cout<<"In MyClass "<<i<<endl; } //Derived.h #pragma once #include "myclass.h" class Derived : public MyClass { public: Derived(void); ~Derived(void); void f(int i=3); }; //Derived.cpp #include "Derived.h" #include<iostream> using namespace std; Derived::Derived(void) { } Derived::~Derived(void) { } void Derived::f(int i) { cout<<"In Derived "<<i<<endl; } //main.cpp #include<iostream> #include"MyClass.h" #include"Derived.h" using namespace std; int main() { MyClass my; Derived de; my.f(); de.f(); MyClass *p=&my; p->f(); p=&de; p->f(); }
运行结果:
从运行结果可以清晰地看书,尽管实现了动态绑定,但是在p指向de以后,调用p->f()使用的参数确是基类中的默认实参。
还要注意的就是,默认实参可以在源文件或者头文件中进行指定,但是只能指定一次。通常应该在函数声明中指定默认实参,并且将该声明放在合适的头文件中。如果未放在头文件,而是放在函数定义(源文件)中,那么,要想使用这个默认实参,必须包含此cpp源文件。