虚函数可以有默认参数吗?

如果我声明一个基类(或接口类)并为它的一个或多个参数指定默认值,派生类是否必须指定相同的默认值?如果不指定,派生类中将显示哪些默认值?

附录:我还感兴趣的是如何在不同的编译器之间处理这个问题,以及在这种情况下“推荐”实践的任何输入。

85311 次浏览

这是一个你可以通过测试很好地解决的问题(也就是说,它是语言的一个足够主流的部分,大多数编译器几乎肯定都是正确的,除非你看到编译器之间的差异,否则它们的输出可以被认为是非常权威的)。

#include <iostream>


struct base {
virtual void x(int a=0) { std::cout << a; }
virtual ~base() {}
};


struct derived1 : base {
void x(int a) { std:: cout << a; }
};


struct derived2 : base {
void x(int a = 1) { std::cout << a; }
};


int main() {
base *b[3];
b[0] = new base;
b[1] = new derived1;
b[2] = new derived2;


for (int i=0; i<3; i++) {
b[i]->x();
delete b[i];
}


derived1 d;
// d.x();       // won't compile.
derived2 d2;
d2.x();
return 0;
}

虚拟机可能有默认值。基类中的默认值不会被派生类继承。

使用哪种默认值——即基类'或派生类'——由用于调用函数的静态类型决定。如果通过基类对象、指针或引用调用,则使用基类中指定的默认值。相反,如果通过派生类对象调用,则使用派生类中表示的默认值的指针或引用。在标准引语下面有一个例子可以说明这一点。

有些编译器可能会做一些不同的事情,但这是c++ 03和c++ 11标准所说的:

8.3.6.10:

虚函数调用(10.3)使用 中的默认实参 虚函数的声明 确定 由指示对象的指针或引用的静态类型。一个 重写派生函数中的函数 类不从函数获取默认实参 覆盖。例子:< / p >
struct A {
virtual void f(int a = 7);
};
struct B : public A {
void f(int a);
};
void m()
{
B* pb = new B;
A* pa = pb;
pa->f(); //OK, calls pa->B::f(7)
pb->f(); //error: wrong number of arguments for B::f()
}

下面是一个示例程序,用于演示选择什么默认值。为了简洁起见,我在这里使用struct而不是classes——classstruct在几乎所有方面都是完全相同的,除了默认可见性。

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>


using std::stringstream;
using std::string;
using std::cout;
using std::endl;


struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };


string Base::Speak(int n)
{
stringstream ss;
ss << "Base " << n;
return ss.str();
}


string Der::Speak(int n)
{
stringstream ss;
ss << "Der " << n;
return ss.str();
}


int main()
{
Base b1;
Der d1;


Base *pb1 = &b1, *pb2 = &d1;
Der *pd1 = &d1;
cout << pb1->Speak() << "\n"    // Base 42
<< pb2->Speak() << "\n"     // Der 42
<< pd1->Speak() << "\n"     // Der 84
<< endl;
}

这个程序的输出(在MSVC10和GCC 4.4上)是:

Base 42
Der 42
Der 84

这是Herb Sutter早期本周大师帖子的主题之一。

他在这个问题上说的第一件事就是不要那样做。

更详细地说,是的,您可以指定不同的默认参数。它们不会像虚函数那样工作。虚函数是在对象的动态类型上调用的,而默认参数值是基于静态类型的。

鉴于

class A {
virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

你应该得到 答::foo1 B: foo2 B:: foo1 < / p >

从其他答案中可以看出,这是一个复杂的问题。而不是尝试这样做或理解它做什么(如果您现在必须询问,维护者将不得不在一年后询问或查找它)。

相反,在基类中使用默认参数创建一个公共非虚函数。然后调用一个没有默认参数的私有或受保护的虚函数,并根据需要在子类中重写。这样你就不必担心它如何工作的细节,代码是非常明显的。

这是一个坏主意,因为你得到的默认参数将依赖于对象的静态类型,而分派给virtual函数将依赖于动态类型。

也就是说,当你调用一个带有默认实参的函数时,默认实参在编译时被替换,不管这个函数是不是virtual

@cppcoder在他的[closed] 问题中提供了以下例子:

struct A {
virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};


int main()
{
A * a = new B();
a->display();


A* aa = new A();
aa->display();


B* bb = new B();
bb->display();
}

它产生以下输出:

Derived::5
Base::5
Derived::9

借助于上面的解释,很容易看出原因。在编译时,编译器替换指针静态类型的成员函数的默认实参,使main函数等价于以下内容:

    A * a = new B();
a->display(5);


A* aa = new A();
aa->display(5);


B* bb = new B();
bb->display(9);

正如其他答案所详述的,这是个坏主意。然而,由于没有人提到简单有效的解决方案,下面是:将参数转换为struct,然后可以将默认值转换为struct成员!

所以,

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

这样做,

//good idea
struct Param1 {
int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)