如果我声明了一个基类(或接口类)并为其一个或多个参数指定了默认值,那么派生类是否必须指定相同的默认值,如果没有,哪些默认值将在派生类中显示?
附录:我也对如何在不同的编译器中处理这个问题以及在这种情况下对“推荐”实践的任何输入感兴趣。
答案 0 :(得分:188)
虚拟可能有默认值。派生类不继承基类中的默认值。
使用哪个默认值 - 即基类'或派生类' - 由用于调用函数的静态类型确定。如果通过基类对象,指针或引用进行调用,则使用基类中表示的缺省值。相反,如果通过派生类对象,指针或引用调用,则使用派生类中表示的默认值。标准报价下面有一个例子可以证明这一点。
有些编译器可能会做一些不同的事情,但这就是C ++ 03和C ++ 11标准所说的:
(编辑:C ++ 11标准完全相同)
虚函数调用(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只是为了简洁 - 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;
}
该程序的输出(在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)