【问题标题】:Why do we need abstract classes in C++?为什么我们需要 C++ 中的抽象类?
【发布时间】:2012-12-20 19:09:02
【问题描述】:

我刚刚了解了我的 OOP 类中的多态性,但我很难理解抽象基类的用途。

抽象类的目的是什么?在每个实际类中创建每个必要的函数时,定义一个抽象基类提供了什么?

【问题讨论】:

  • 重点是你在每个类中创建函数。它由编译器强制执行。

标签: c++ oop inheritance abstract-class


【解决方案1】:

抽象类的目的是为一组具体的子类定义一个通用协议。这在定义共享代码、抽象概念等的对象时很有用。

抽象类没有实例。一个抽象类必须至少有一个延迟方法(或函数)。为了在 C++ 中实现这一点,在抽象类中声明但未定义纯虚成员函数:

class MyClass {
    virtual void pureVirtualFunction() = 0;
}

尝试实例化抽象类总是会导致编译器错误。

"定义一个抽象基类提供了哪些没有提供的东西 通过在每个实际类中创建每个必要的函数?”

这里的主要思想是代码重用和跨类的适当分区。在父类中定义一次函数比在多个子类中一遍又一遍地定义更有意义:

class A {
   void func1();
   virtual void func2() = 0;
}

class B : public A {
   // inherits A's func1()
   virtual void func2();   // Function defined in implementation file
}

class C : public A {
   // inherits A's func1()
   virtual void func2();   // Function defined in implementation file
}

【讨论】:

    【解决方案2】:

    拥有一个像“Dog”这样的抽象类和一个像“bark”这样的虚拟方法允许所有从 Dog 继承的类都以相同的方式调用它们的 bark 代码,即使 Beagle 的 bark 的实现方式与 Collie 的不同。

    如果没有一个共同的抽象父对象(或至少一个具有 bark 虚拟方法的共同父对象),就很难做到以下几点:

    有一个 Dog 类型的向量,其中包含 Collies、Beagles、German Shepherds 等,并让它们每个都吠叫。使用包含 Collies、Beagles、German Shepherds 的 Dogs 向量,要让它们全部吠叫,您要做的就是在 for 循环中迭代并在每一条上调用 bark。否则你必须有一个单独的柯利犬向量、比格犬向量等。

    如果问题是“为什么在 Dog 可以是具体的情况下将其抽象化,是否定义了一个可以覆盖默认实现的虚拟树皮?”,答案是有时这可能是可以接受的——但是,从从设计的角度来看,真的没有任何像狗这样的东西,它不是柯利犬、比格犬或其他品种或混合体,所以尽管它们都是狗,但实际上没有一只是狗,但不是还有一些其他派生类。此外,由于狗的叫声因一个品种而异,因此不太可能有任何真正可接受的默认树皮实现对于任何体面的狗群都是可以接受的。

    我希望这可以帮助您理解其目的:是的,无论如何您都必须在每个子类中实现 bark,但是公共抽象祖先允许您将任何子类视为基类的成员并调用可能的行为在概念上类似于树皮,但实际上有非常不同的实现。

    【讨论】:

    • 我喜欢这个答案
    • 感谢您使用 Dog 的示例。它使概念非常清晰易懂
    【解决方案3】:

    抽象类允许执行编译时协议。这些协议定义了成为类族的一部分意味着什么。

    另一种思考方式是抽象类是您的实现类必须履行的契约。如果他们不履行此合同,他们就不能成为班级家庭的一部分,并且必须对其进行修改以符合合同。提供的契约可以提供默认功能,但它也留给子类来定义更具体或不同的功能,同时仍保留在契约的范围内。

    对于小型项目,这似乎没有用,但对于大型项目,它提供了一致性和结构,因为它通过抽象类契约提供文档。这使得代码更易于维护,并使每个子类具有相同的协议,从而更容易使用和开发新的子类。

    【讨论】:

      【解决方案4】:

      我有一条狗。具有方法吠叫的抽象类狗。我那只特别的狗叫一声。其他狗以不同的方式吠叫。所以用抽象的方式定义狗是很有用的。

      【讨论】:

      • 当我们要在派生类中重写bark方法时,实际上是在每个派生类中创建函数bark。那么你能告诉我为什么使用它很重要的具体原因吗?
      • 狗必须实现吠叫功能的特定合约的固有类。否则,您将无法启动课程。假设子类由第三方实现。这种抽象机制保证了 bark 由第三方实现,从抽象狗的角度调用 bark 函数时可以安全地使用多态性。
      【解决方案5】:

      抽象类的目的是提供一个适当的基类,其他类可以从该基类继承。抽象类不能用于实例化对象,只能用作接口。尝试实例化抽象类的对象会导致编译错误。 (因为我们在抽象类中提到的虚函数的内存位置没有填充vtable条目)

      因此,如果需要实例化ABC的子类,则必须实现每个虚函数,这意味着它支持ABC声明的接口。未能覆盖派生类中的纯虚函数,然后尝试实例化该类的对象,是编译错误。

      例子:

      class mobileinternet
      {
      public:
      virtual enableinternet()=0;//defines as virtual so that each class can overwrite
      };
      
      
      class 2gplan : public mobileinternet
      
      {
          private:
      
               int providelowspeedinternet(); //logic to give less speed.
      
          public:
      
               void enableinternet(int) {
                                           // implement logic
                                       }
      
      };
      
      //similarly
      
      class 3gplan : public enableinternet
      {
         private: high speed logic (different then both of the above)
      
         public: 
                /*    */
      }
      

      在这个例子中,你可以理解。

      【讨论】:

        【解决方案6】:

        抽象类用于定义要实现的接口。查看一些参考资料:

        http://en.wikibooks.org/wiki/C%2B%2B_Programming/Classes/Abstract_Classes

        【讨论】:

          【解决方案7】:

          当所有具有从AbstractClass 派生的类型的对象都需要某种功能时,需要一个抽象类AbstractClass 作为基类,但不能在AbstractClass 本身上合理地实现。

          具有基类Vehicle 和派生类CarMotorcycle 的旧且有些人为的OO 示例在这里提供了一个很好的示例,假设您想要一个方法move() - 您可以实现CarMotorcycle 移动的方式,但 Vehicles 不会以通用方式移动,因此 Vehicle::move() 必须是纯虚拟的,Vehicle 因此是抽象的。

          【讨论】:

            【解决方案8】:

            我们为什么不在每个类中创建每个必要的函数? (C++)

            您必须在每个派生类中创建标记为abstract 的每个必要函数。

            如果你的问题是,为什么要在抽象类中创建抽象函数?

            它允许严格的run time polymorphism

            另请阅读Interface vs Abstract Class (general OO)

            【讨论】:

            • 很好,我明白了,如果我使用抽象类,我可以将它们归为一组。我可以通过使用带有基指针的静态和动态转换来转换它们。
            【解决方案9】:
            abstract class dog
            {
            bark();
            }
            
            // function inside another module
            
            dogbarking(dog obj)
            {
               dog.bark(); // function will call depend up on address inside the obj
            }
            
            
            // our class
            ourclass: inherit dog
            {
                bark()
                {
                     //body
                 }
            }
            
            
            main()
            {
                ourclass obj;
                dogbarking(obj);
            }
            

            我们可以看到 dogbarking 是在另一个模块中编写的函数。它只知道抽象类狗。即使它可以在我们的类中调用函数 bark。在主函数中,我们创建类的对象并传递给函数 dogbarking,它使用抽象类 dog 的引用对象接收它。

            【讨论】:

              【解决方案10】:

              假设您有两种显示字符串的方法:

              DisplayDialog(string s);
              PrintToConsole(string s);
              

              而你想写一些可以在这两种方法之间切换的代码:

              void foo(bool useDialogs) {
                  if (useDialogs) {
                      DisplayDialog("Hello, World!");
                  } else {
                      PrintToConsole("Hello, World!");
                  }
              
                  if (useDialogs) {
                      DisplayDialog("The result of 2 * 3 is ");
                  } else {
                      PrintToConsole("The result of 2 * 3 is ");
                  }
              
                  int i = 2 * 3;
                  string s = to_string(i);
              
                  if (useDialogs) {
                      DisplayDialog(s);
                  } else {
                      PrintToConsole(s);
                  }        
              }
              

              此代码与用于显示字符串的特定方法紧密耦合。添加一个额外的方法、改变方法的选择方式等等都会影响使用它的每一段代码。此代码与我们用来显示字符串的方法集紧密耦合

              抽象基类是一种解耦代码的方式,它使用实现该功能的代码中的某些功能。它通过为完成任务的所有不同方式定义一个通用接口来实现这一点。

              class AbstractStringDisplayer {
              public:
                  virtual display(string s) = 0;
              
                  virtual ~AbstractStringDisplayer();
              };
              
              void foo(AbstractStringDisplayer *asd) {
                  asd->display("Hello, World!");
                  asd->display("The result of 2 * 3 is ");
              
                  int i = 2 * 3;
                  string s = to_string(i);
              
                  asd->display(s);
              }
              
              int main() {
                  AbstractStringDisplayer *asd = getStringDisplayerBasedOnUserPreferencesOrWhatever();
              
                  foo(asd);
              }
              

              使用 AbstractStringDisplayer 定义的接口,我们可以创建和使用尽可能多的新方式来显示字符串,并且不需要更改使用抽象接口的代码。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-04-03
                • 1970-01-01
                • 2015-06-05
                • 1970-01-01
                • 2017-01-04
                • 2012-03-30
                • 2013-02-15
                • 1970-01-01
                相关资源
                最近更新 更多