C++ 虚拟函数可以有默认参数吗?
如果我声明一个基类(或接口类)并为其一个或多个参数指定一个默认值,那么派生类是否必须指定相同的默认值?如果不是,哪些默认值将显示在派生类中C++ 虚拟函数可以有默认参数吗?,c++,c++11,c++03,C++,C++11,C++03,如果我声明一个基类(或接口类)并为其一个或多个参数指定一个默认值,那么派生类是否必须指定相同的默认值?如果不是,哪些默认值将显示在派生类中 附录:我还对如何跨不同的编译器处理这一问题感兴趣,以及在这个场景中“推荐”实践的任何输入。这是一个您可以通过测试很好地理解的问题(也就是说,它是语言的一个足够主流的部分,大多数编译器几乎肯定都能正确使用它,除非您看到编译器之间的差异,否则它们的输出可以被认为是相当权威的) #包括 结构基{ 虚拟void x(int a=0){std::cout虚拟对象可能有
附录:我还对如何跨不同的编译器处理这一问题感兴趣,以及在这个场景中“推荐”实践的任何输入。这是一个您可以通过测试很好地理解的问题(也就是说,它是语言的一个足够主流的部分,大多数编译器几乎肯定都能正确使用它,除非您看到编译器之间的差异,否则它们的输出可以被认为是相当权威的)
#包括
结构基{
虚拟void x(int a=0){std::cout虚拟对象可能有默认值。基类中的默认值不会被派生类继承
使用哪个默认值(即基类“或派生类”)取决于用于调用函数的静态类型。如果通过基类对象、指针或引用调用,则使用基类中表示的默认值。相反,如果通过派生类对象、指针或引用调用,则默认值为在派生类中使用ted。下面的标准引用中有一个示例演示了这一点
有些编译器可能会做一些不同的事情,但这是C++03和C++11标准所说的:
8.3.6.10:
虚拟函数调用(10.3)使用
中的默认参数
虚函数的声明
决心
通过表示对象的指针或引用的静态类型
派生函数中的重写函数
类不会从它所属的函数获取默认参数
覆盖。例如:
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
s,而不是class
es,只是为了简洁--class
和struct
除了默认可见性之外,几乎在所有方面都完全相同
#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;
}
这是赫伯·萨特早期一篇文章的主题
关于这个问题,他说的第一件事就是不要那样做
更详细地说,是的,您可以指定不同的默认参数。它们的工作方式与虚拟函数不同。对对象的动态类型调用虚拟函数,而默认参数值基于静态类型
给定
A类{
虚拟void foo(inti=1){cout正如您从其他答案中看到的,这是一个复杂的问题。与其尝试这样做或理解它的作用(如果您现在必须询问,维护人员将不得不在一年后询问或查找)
相反,在基类中创建一个带有默认参数的公共非虚拟函数。然后它调用一个没有默认参数的私有或受保护的虚拟函数,并根据需要在子类中重写。这样,您就不必担心它如何工作的细节,代码也很明显。这是个坏主意,因为您获得的默认参数将取决于对象的静态类型,而分派到的virtual
函数将取决于动态类型
也就是说,当使用默认参数调用函数时,无论函数是否为virtual
,默认参数都会在编译时被替换
@cppcoder在其[closed]中提供了以下示例:
借助上面的解释,很容易看出原因。在编译时,编译器替换指针静态类型的成员函数的默认参数,使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)
这似乎是一件容易测试的事情。你试过了吗?我正在尝试,但我还没有找到如何“定义”的具体信息行为是这样的,我最终会为我的特定编译器找到一个答案,但这不会告诉我所有编译器是否都会做同样的事情。我也对推荐的实践感兴趣。行为定义良好,我怀疑你是否会找到一个错误的编译器(如果你测试gcc 1.x或VC++1.0或类似的东西)。建议的做法是反对这样做。@GMan:[仔细观察无辜者]什么泄漏?:-)我想他指的是缺少虚拟析构函数。但在这种情况下它不会泄漏。@Jerry,如果您通过基类指针删除派生对象,析构函数是虚拟的。否则,所有的派生对象都将调用基类析构函数。在这种情况下,没有析构函数是可以的。:-@John:最初没有删除,这是错误的我指的是。我完全忽略了虚拟析构函数的缺乏。而且…@chappar:不,这不好。它必须有一个虚拟析构函数通过基类删除,否则你会得到未定义的行为。(这段代码有未定义的行为。)它与派生类所具有的数据或析构函数无关。@Chappar:代码最初没有删除任何内容。虽然这与当前的问题基本无关,但我还向基类添加了一个虚拟dtor——使用一个微不足道的dtor,这几乎不重要,但GMan完全正确,没有它,代码就具有UB.Thank.a赫伯·萨特(Herb Sutter)的“不要那样做”有一定的份量。@ArnoldSpence,事实上赫伯·萨特(Herb Sutter)超出了这个建议。他认为接口根本不应该包含虚拟方法:。一旦你的方法是具体的并且不能(不应该)被重写,就可以安全地给它们默认参数。我相信他所说的“不要那样做”是正确的“不要更改
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
A * a = new B();
a->display(5);
A* aa = new A();
aa->display(5);
B* bb = new B();
bb->display(9);
//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)