【问题标题】:When should a virtual method be pure?什么时候虚方法应该是纯的?
【发布时间】:2011-10-14 04:05:21
【问题描述】:

我找到了一些我正在处理的代码,并且想知道最好的设计实现是什么。

如果一个基类定义了一个虚方法,但也实现了一个空体,因此派生类不需要实现一个体,难道不应该改为纯方法吗?

virtual void AMethod1() {}                 // 1
virtual void AMethod2() {assert(false);}   // 2
virtual void AMethod3() = 0;               // 3
  1. 当前代码。
  2. Idea1:提醒用户此派生对象尚未实现此方法主体。
  3. Idea2:强制派生类实现一个主体,无论是否为空。

你们这些值得信赖的、了不起的 SO 人是怎么想的?


Edit1:发布(并阅读答案)后,我意识到断言很糟糕!

virtual void AMethod3() = {throw (ENotImplemented)};               // 4

【问题讨论】:

  • 请注意,您实际上可以拥有一个带有主体的纯虚方法(它不能是内联的)。
  • 您能否详细说明为什么您认为assert 比例外更糟糕?在手头的情况下,我觉得它非常合适:如果必须始终重写方法并且从不实例化基类,那么应该从不调用默认实现,这是程序的不变量(不是可以异常发生的事件)。当然,如果有这些要求,最好将方法设为纯虚拟。
  • @Let_Me_Be 谢谢你,我认为 larsmans 用你所说的例子给出了答案......
  • @Luc Touraille 也许是因为assert() 仅在调试模式下检查,所以通常在发布版本中关闭。
  • @Archie:我不确定这是否真的是一个问题,因为断言仅在开发期间有用(它针对扩展基类的开发人员)。它用于警告开发人员他们的设计中存在错误,一旦更正了断言就不应被触发,因此之后将其关闭应该没有害处。

标签: c++ methods derived-class pure-virtual


【解决方案1】:

我见过很多这样的例子,你需要实例化类,所以 你使用 virtual 和一个空的身体:

virtual void AMethod1() {}                 // 1

当你想强制派生类覆盖时使用这个 这个函数,你不需要默认值:

virtual void AMethod3() = 0;               // 3

所以这真的取决于你想做什么。

【讨论】:

    【解决方案2】:

    如果基类定义了一个虚方法,但也实现了一个空体,因此不需要派生类实现一个体,它不应该改为纯方法吗?

    这取决于您是否要强制派生类覆盖该方法。如果你这样做,那么使用纯virtual;它正是对这一要求的语言支持。如果有或以后可能有amethod 的默认实现,则使用带有实现的纯virtual 方法:

    class Foo {
        virtual void amethod() = 0;
    };
    
    void Foo::amethod() {
        // whatever
    }
    

    该函数现在仍然是纯 virtual,因此无法实例化 Foo 类,但任何派生类都将继承实现,其方法可以将其调用为 Foo::amethod

    【讨论】:

    • 哦,我不知道我可以用实现来做纯虚拟! virtual void Foo::amethod() 谢谢。
    • larsmans 谢谢你的好例子,但你仍然必须覆盖派生类中的纯虚函数,即使现在你可以简单地调用派生类中实现的纯虚函数作为默认值,如果那是什么you want.something like: class derived : public Foo{ public: void amethod(){ // 此处仍需要覆盖 Foo::amethod(); } };
    • @Gob00st:没错,它仍然是纯粹的virtual。我会澄清一下我的答案。
    【解决方案3】:

    没有简单的规则:

    如果对某些派生类有意义,则使用 1(空实现) 如果函数被调用,什么也不做。

    如果派生类不实现 功能。

    在极少数情况下使用 2,其中函数的先决条件是 另一个虚函数 hare 返回 true,并且该函数具有 默认实现返回 false (或类似的东西)。 基本上,如果接口的一部分是可选的。 (但通常,它是 在这种情况下更好地派生接口;类实现 扩展接口派生自它,以及想要使用它的客户 dynamic_cast 到扩展接口。)

    根据经验(但您的编程风格可能不同),1 似乎至少在 90% 的情况下都适用,而且我认为在 C++ 的 20 多年中,我使用过 3 一次,或者可能两次。

    【讨论】:

      【解决方案4】:

      因为你需要virtual机制;以下是我的简短回答:

      (1)virtual void AMethod1() {}

      要求:

      - Allow creating objects of base class
      - Base `virtual` method is use.
      

      (2) virtual void AMethod2() {assert(false);}

      要求:

      - Allow creating objects of base class
      - Base method is not used
      - Force derived classes to implement the method (hard way, because it happens at runtime).
      

      (3) virtual void AMethod3() = 0;

      要求:

      - Don't allow base class object creation
      - Force derived classes to implement the method at compile time
      

      【讨论】:

        【解决方案5】:
        • virtual void AMethod1() = 0;:当你的基类没有实现来提供 AND 而这种行为应该 时,纯虚拟是最好的> 实施。 (这是您问题中的选项 3)

        • virtual void AMethod1() {}:当您的基类没有实现来提供 AND 时,具有空实现的虚拟是最好的,而这种行为可能 实施。 (这是您问题中的选项 1)

        • virtual void AMethod1() { assert(false); }:我认为必须避免使用带有assert(false) 的虚拟。我看不出它有任何有效的用途。其背后的基本原理是所有用例都包含在上面的两个选项中:行为 ma​​yshould 被实现,因此没有必要定义可调用方法那总是失败。编译器可以通过阻止此调用为您处理此问题,因此此选项通过将此检查推迟到运行时来引入风险。 (这是您问题中的选项 2)

        【讨论】:

          【解决方案6】:

          如果基类将方法定义为虚拟方法,但实现了空方法 body 也是如此,因此不需要派生类实现 身体,难道不应该是纯洁的吗?

          这取决于您的设计。如果您的方法是纯虚拟方法,则您正在向派生类开发人员发送一条消息,上面写着“您必须在此处放置一些实际代码以使您的类正常工作”。另一方面,如果您的方法是具有空主体的虚拟方法,则消息是“您可以在此处放置一些代码,但这取决于您的实际需求”。

          virtual void AMethod1() {}                 // 1
          virtual void AMethod2() {assert(false);}   // 2
          virtual void AMethod3() = 0;               // 3
          

          我肯定更喜欢选项 3 而不是 2,如果派生类没有实现虚方法,它将产生编译错误而不是运行时错误。

          【讨论】:

            【解决方案7】:

            如果派生类必须实现此方法,则应使用选项 3。如果在派生类中实现是可选的,请使用选项 1。完全避免选项 2。

            【讨论】:

            • 我大概知道,我想知道是否有首选实现设计?但正如您在其他答案中所说,这取决于编码风格
            • @Ian:我真的不认为这是编码风格的问题,而是更多的语义问题:拥有默认实现有意义吗?实例化类有意义吗?如果没有覆盖,我是否希望隐式使用默认实现,或者如果派生类想要使用它,是否应该隐式调用它? (嗯,最后一个问题可能只是风格问题)。不可能有真正的首选设计,因为它们不能实现相同的目标:选择一个或另一个取决于您的要求。
            【解决方案8】:

            制作方法pure virtual 比使用assert 制作默认实现更直观。如果 什么都不做 在大多数情况下是默认实现,则当前代码会更好。当然,如果你想使用多态,它应该保持虚拟。

            【讨论】:

              【解决方案9】:

              这在一定程度上取决于您的编码风格有多“纯粹”。有些人认为您应该始终只使用纯虚函数定义一个接口,并从中派生所有具体类。

              其他人更务实,相信如果有一个好的默认实现,你可以将它添加到基类中(选项 1)。

              第二个选项似乎最没用,因为它将检测延迟到运行时。大多数程序员宁愿选择选项 3 中的编译错误。

              像往常一样,C++ 支持多种范例,您可以选择自己喜欢的一种。

              【讨论】:

              • "它将检测延迟到运行时" - 如果(并且我认为只有在)存在创建对象并使用其他成员函数但避免调用 @987654321 的情况下,这将更有用@。可能是一个设计缺陷,因为如果您仅限于使用已定义 API 的一个子集,也许您应该正式定义另一个更小的接口。但它会飞。
              【解决方案10】:

              如果我们不使用,我们应该使用纯虚函数 想要实例化一个类,但让它充当一个基类 对于从它派生的所有类。

              一件重要的事 关于纯虚函数要注意的是这些 必须在所有派生类中重写函数 否则编译会标记出错误。

              简单例如,

              class alpha     {
                   public:virtual void show()=0; //pure virtual function
                  };
              
              class beta : public alpha {
              
                   public:void show()   //overriding
                      {
                       cout<<"OOP in C++";
                      }
                  };
              void main() {
                   alpha *p;
                   beta b;
                   p=&b;
                   p->show();
                 }
              

              【讨论】:

              • 我认为这是一针见血如果不想实例化一个类但要执行基类,则纯虚拟
              猜你喜欢
              • 1970-01-01
              • 2012-03-23
              • 2019-12-26
              • 1970-01-01
              • 1970-01-01
              • 2012-09-15
              • 2016-06-27
              • 2011-03-12
              • 2010-09-17
              相关资源
              最近更新 更多