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

发布于 2024-09-15 07:15:11 字数 125 浏览 3 评论 0原文

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

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

If I declare a base class (or interface class) and specify a default value for one or more of its parameters, do the derived classes have to specify the same defaults and if not, which defaults will manifest in the derived classes?

Addendum: I'm also interested in how this may be handled across different compilers and any input on "recommended" practice in this scenario.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

抚你发端 2024-09-22 07:15:12

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

使用哪个默认值——即基类“还是派生类”——由用于调用函数的静态类型决定。如果通过基类对象、指针或引用进行调用,则使用基类中表示的默认值。相反,如果通过派生类对象、指针或引用进行调用,则使用派生类中表示的默认值。标准报价下方有一个示例说明了这一点。

某些编译器可能会执行不同的操作,但这就是 C++03 和 C++11 标准的规定:

8.3.6.10:

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

<前><代码>结构A {
虚拟无效 f(int a = 7);
};
结构 B :公共 A {
无效 f(int a);
};
无效 m()
{
B* pb = 新 B;
A* pa = pb;
pa->f(); //好的,调用 pa->B::f(7)
pb->f(); //错误:B::f() 的参数数量错误
}


这是一个示例程序,用于演示采用的默认值。为了简洁起见,我在这里使用 struct 而不是 class —— 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

Virtuals may have defaults. The defaults in the base class are not inherited by derived classes.

Which default is used -- ie, the base class' or a derived class' -- is determined by the static type used to make the call to the function. If you call through a base class object, pointer or reference, the default denoted in the base class is used. Conversely, if you call through a derived class object, pointer or reference the defaults denoted in the derived class are used. There is an example below the Standard quotation that demonstrates this.

Some compilers may do something different, but this is what the C++03 and C++11 Standards say:

8.3.6.10:

A virtual function call (10.3) uses
the default arguments in the
declaration of the virtual function
determined
by the static type of the pointer or reference denoting the object. An
overriding function in a derived
class does not acquire default arguments from the function it
overrides. Example:

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()
}

Here is a sample program to demonstrate what defaults are picked up. I'm using structs here rather than classes simply for brevity -- class and struct are exactly the same in almost every way except default visibility.

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

The output of this program (on MSVC10 and GCC 4.4) is:

Base 42
Der 42
Der 84
画中仙 2024-09-22 07:15:12

这是 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

This was the topic of one of Herb Sutter's early Guru of the Week posts.

The first thing he says on the subject is DON'T DO THAT.

In more detail, yes, you can specify different default parameters. They won't work the same way as the virtual functions. A virtual function is called on the dynamic type of the object, while the default parameter values are based on the static type.

Given

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

you should get
A::foo1
B::foo2
B::foo1

你的他你的她 2024-09-22 07:15:12

这是一个坏主意,因为您获得的默认参数将取决于对象的静态类型,而分派到的虚拟函数将取决于动态 类型。

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

@cppcoder 在他的[已关闭]问题中提供了以下示例:

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

This is a bad idea, because the default arguments you get will depend on the static type of the object, whereas the virtual function dispatched to will depend on the dynamic type.

That is to say, when you call a function with default arguments, the default arguments are substituted at compile time, regardless of whether the function is virtual or not.

@cppcoder offered the following example in his [closed] 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();
}

Which produces the following output:

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

With the aid of the explanation above, it is easy to see why. At compile time, the compiler substitutes the default arguments from the member functions of the static types of the pointers, making the main function equivalent to the following:

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

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

    B* bb = new B();
    bb->display(9);
楠木可依 2024-09-22 07:15:12

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

所以,不要这样做

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

As other answers have detailed, its bad idea. However since no one mentions simple and effective solution, here it is: Convert your parameters to struct and then you can have default values to struct members!

So instead of,

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

do this,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
夏日落 2024-09-22 07:15:12

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

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

As you can see from the other answers this is a complicated subject. Instead of trying to do this or understand what it does (if you have to ask now, the maintainer will have to ask or look it up a year from now).

Instead, create a public non-virtual function in the base class with default parameters. Then it calls a private or protected virtual function that has no default parameters and is overridden in child classes as needed. Then you don't have to worry about the particulars of how it would work and the code is very obvious.

情栀口红 2024-09-22 07:15:12

您可能可以通过测试很好地弄清楚这一点(即,它是该语言的足够主流的部分,大多数编译器几乎肯定都能正确执行,除非您看到编译器之间的差异,否则它们的输出可以被认为是相当权威的)。

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

This is one that you can probably figure out reasonably well by testing (i.e., it's a sufficiently mainstream part of the language that most compilers almost certainly get it right and unless you see differences between compilers, their output can be considered pretty well authoritative).

#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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文