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)