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

时间:2010-08-20 18:04:26

标签: c++ c++11 c++03

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

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

6 个答案:

答案 0 :(得分:188)

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

使用哪个默认值 - 即基类'或派生类' - 由用于调用函数的静态类型确定。如果通过基类对象,指针或引用进行调用,则使用基类中表示的缺省值。相反,如果通过派生类对象,指针或引用调用,则使用派生类中表示的默认值。标准报价下面有一个例子可以证明这一点。

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

编辑:C ++ 11标准完全相同)

8.3.6.10:

  

虚函数调用(10.3)使用   中的默认参数   声明虚函数   决心       通过表示对象的指针或引用的静态类型。一个   在派生中覆盖函数       class不从函数中获取默认参数   覆盖。 [实施例:

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()
}
—end example]

编辑以下是一个示例程序,用于演示拾取的默认值。我在这里使用struct而不是class es只是为了简洁 - 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

答案 1 :(得分:34)

这是Herb Sutter早期Guru of the Week帖子之一的主题。

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

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

鉴于

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();
}
你应该得到     A :: foo1     B :: foo2的     B :: foo1

答案 2 :(得分:4)

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

#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;
}

答案 3 :(得分:3)

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

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

答案 4 :(得分:3)

这是一个坏主意,因为您获得的默认参数将取决于对象的 static 类型,而调度到的virtual函数将取决于动态类型。

也就是说,当您使用默认参数调用函数时,无论函数是否为virtual,都会在编译时替换默认参数。

@cppcoder在[关闭] question中提供了以下示例:

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);

答案 5 :(得分:1)

正如其他答案详述,其不好的主意。但是,由于没有人提到简单有效的解决方案,这里是:将您的参数转换为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)