【问题标题】:Good practice : Default arguments for pure virtual method良好实践:纯虚方法的默认参数
【发布时间】:2012-08-27 09:56:15
【问题描述】:

我创建了一个抽象基类,它有一个带有默认参数的纯虚方法。

class Base {
    ...
    virtual someMethod(const SomeStruct& t = 0) = 0;
    ...
}

class Derived : public Base {
    ...
    virtual someMethod(const SomeStruct& t = 0);
    ...
}

所以我想知道将默认参数设置为纯虚拟方法并将整体设置为虚拟方法是否是一种好习惯?

【问题讨论】:

  • 我想你的意思是 const SomeStruct* t = 0?
  • @luskan: SomeStruct 可以从 0 隐式转换。
  • “将默认参数设置为纯虚拟”是什么意思?
  • 我的派生类 someMethod 应该有默认参数,这是基类的纯虚拟接口的实现。通过将默认参数设置为纯虚拟方法,我的意思就是我在示例中写的内容。

标签: c++


【解决方案1】:

实际上,您的代码是默认参数最糟糕的使用模式之一,因为它涉及继承和多态行为。我支持查看相关的 Scott Meyers 提示的建议,但这里有一个简短的概述:

在多态调用的情况下,根据静态类型的声明使用默认参数,而不是动态的。这是合乎逻辑的,因为运行时不知道默认参数,但打破了关于多态行为的任何合理假设。例如,

#include <cstdio>

class Base
{
        public:
                virtual void f(int a = 1)
                {
                        printf("Base::f(%d)\n", a);
                }
};

class Deriv : public Base
{
        public:
                virtual void f(int a = 2)
                {
                        printf("Deriv::f(%d)\n", a);
                }
};

int main()
{
        Base* a = new Deriv();
        a->f();
        delete a;
        return 0;
}

产量:

Deriv::f(1)

【讨论】:

  • 很好的例子展示了与这种方法相关的缺陷。
  • 但问题是关于基类中的纯虚方法。
  • 我认为这个理由仍然成立。派生类覆盖了参数的默认值,但是由于a的静态类型,我们最终使用了纯虚声明中提供的默认值。
【解决方案2】:

我经常希望像你一样同时使用默认参数和虚函数。然而,其他人正确地指出,这会导致模棱两可,通常不是一个好主意。有一个相当简单的解决方案,我使用的一个。给你的虚函数一个不同的名字,使其受到保护,然后提供一个带有默认参数的公共函数来调用它。

class Base {
protected:
    virtual void vSomeMethod(const SomeStruct& t ) = 0;
public:
    void someMethod( const SomeStruc& t = 0 )
    { vSomeMethod( t ); }
}

派生类只需覆盖vSomeMethod,完全不用担心默认参数。

【讨论】:

  • 与其引入另一个名字“vSomeMethod”,为什么不直接使用重载呢? IE。将公共非虚拟成员函数 void someMethod() 添加到调用 someMethod(0);Base。在更一般的情况下,您想要一个可以使用n+1 默认参数值xn=vn,...,x1=v1,x0=v0 调用的虚拟成员函数f(xn,...,x1,x0)f 的虚拟重载可能没有默认值,并且可能存在非使用n 默认参数f(xn=vn,...,x1=v1) 调用f(xn,...,x1,v0) 的虚拟重载。
  • @Ose 使用单独命名的实现函数而不是重载同名的原因是因为当您在子类中覆盖虚拟实现时,您在父类中隐藏了非虚拟助手,需要您另外using那个方法进入孩子。
【解决方案3】:

尽可能不要使用默认参数,但如果你这样做了,永远不要重新定义它们 (see the text for details)

购买两本 Scott Meyers 的 Effective C++ 书籍。你不会后悔的。

【讨论】:

  • 我在两种方法中都设置了相同的默认值,我认为您的参考是关于可以将不同值设置为默认参数时的模棱两可的情况。
  • deimus,真的没关系。即使您认为您将始终使用与默认参数相同的值,但很容易打错字或忘记提及默认参数。使用任何适当的代码库,调试这种错误会让你的生活变得非常痛苦。
  • @deimus 你不能保证将来有人不会从你的基类继承并更改默认值。
【解决方案4】:

我愿意:

  • 用参数定义虚函数(无默认值)
  • 在基类中定义非虚函数,处不带参数 所有,调用传递正确默认参数的虚函数

这样派生类根本不用关心默认值。

【讨论】:

    【解决方案5】:

    如果您希望这段代码有意义:

    Base* p = new Derived;
    p->someMethod();
    

    因为p 的静态类型是Base*,所以它是在调用时使用的基本签名。 分配了默认值,并且作为虚拟函数,调用被重定向到 Derived。

    如果您希望 Derived::someMethod 从 Base* 而不是 Derived* 接收不同的值,您甚至可以对它们进行不同的定义...

    重要的是要很好地记录这些“关系”,因为大多数程序员不会通过简单的代码阅读来理解它们。

    当然,如果所有这些都不适合您的特定上下文,会产生更多的混乱,请避免使用虚拟函数的默认参数,并使用辅助非虚拟函数来正确调用它们。

    但也考虑到 - 从读者的角度来看 - 默认参数比一个重载函数私下调用另一个具有不可读参数返工的重载函数更具解释性。

    【讨论】:

      猜你喜欢
      • 2011-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-18
      • 2016-04-29
      • 1970-01-01
      • 1970-01-01
      • 2021-01-03
      相关资源
      最近更新 更多