【问题标题】:Why can't we create objects for an abstract class in C++?为什么我们不能在 C++ 中为抽象类创建对象?
【发布时间】:2010-01-19 05:56:33
【问题描述】:

我知道它在 C++ 中是不允许的,但为什么呢?如果允许,会出现什么问题?

【问题讨论】:

  • 显然某处的沟通存在差距。你能给出一个代码示例和你的高级目标吗?
  • @GMan 我在编写一些代码时遇到了这个问题。我只想知道为什么我们不能为抽象类创建对象的具体原因?问题与内存分配有关,还是与其他问题有关?究竟是什么问题?这就是我的重点。
  • 因为允许它是不合逻辑的。这就像要求 XXX(在这里选择你的神)创建一个动物(一个实例)。那是不可能的,他/她/它必须创建特定类型的动物(如“鸭子”:鸭类的一个实例)。动物的概念是抽象的,没有任何物体只是动物。

标签: c++ object abstract-class


【解决方案1】:

从您的other question 来看,您似乎不了解课程的运作方式。类是对数据进行操作的函数的集合。

函数本身在类中不包含内存。以下类:

struct dumb_class
{
    void foo(){}
    void bar(){}
    void baz(){}
    // .. for all eternity

    int i;
};

大小为int。不管你有多少功能,这个类只会占用在int上运行所需的空间。当你在这个类中调用一个函数时,编译器会给你一个指向类中数据存储位置的指针;这是this 指针。

因此,该函数位于内存中的某个位置,在程序开始时加载一次,然后等待调用数据以进行操作。

虚函数不同。 C++ 标准没有规定虚函数的行为应该如何进行,只是该行为应该是什么。通常,实现使用所谓的虚拟表,或简称为 vtable。 vtable 是一个函数指针表,与普通函数一样,只分配一次。

学习这个类,并假设我们的实现者使用 vtables:

struct base { virtual void foo(void); };
struct derived { virtual void foo(void); };

编译器将需要创建两个 vtable,一个用于基础,一个用于派生。它们看起来像这样:

typedef /* some generic function pointer type */ func_ptr;

func_ptr __baseTable[] = {&base::foo}; 
func_ptr __derivedTable[] = {&derived::foo}; 

它如何使用这张桌子?当您在上面创建类的实例时,编译器会滑入一个隐藏指针,该指针将指向正确的 vtable。所以当你说:

derived d;
base* b = &d;
b->foo();

在执行最后一行时,它会转到正确的表(在本例中为__derivedTable),转到正确的索引(在本例中为 0),然后调用该函数。如您所见,这最终会调用derived::foo,这正是应该发生的。

注意,稍后,这与derived::foo(b) 相同,将b 作为this 指针传递。

因此,当存在虚方法时,大小的类将增加一个指针(指向 vtable 的指针)。多重继承对此有所改变,但基本相同。您可以通过C++-FAQ获取更多详细信息。

现在,回答你的问题。我有:

struct base { virtual void foo(void) = 0; }; // notice the = 0
struct derived { virtual void foo(void); };

并且base::foo 没有实现。这使得base::foo 成为一个纯抽象函数。所以,如果我这样称呼它,就像上面一样:

derived d;
base* b = &d;
base::foo(b);

我们应该期待什么样的行为?作为纯虚方法,base::foo 甚至不存在。上面的代码是未定义的行为,可以做任何事情,从什么都不做到崩溃,介于两者之间。 (或者更糟。)

想想纯抽象函数代表什么。请记住,函数不接受数据,它们只描述如何操作数据。一个纯抽象函数说:“我想调用这个方法并让我的数据被操纵。你怎么做这取决于你。”

因此,当您说“好吧,让我们调用一个抽象方法”时,您是在回复上面的内容:“取决于我?不,你来做。”它将回复“@#^@#^”。告诉那些说“这样做”、“不”的人根本没有意义。

直接回答您的问题:

“为什么我们不能为抽象类创建对象?”

希望您现在看到,抽象类只定义了具体类应该能够执行的功能。抽象类本身只是一个蓝图;你不是住在蓝图里,而是住在执行蓝图的房子里。

【讨论】:

    【解决方案2】:

    问题很简单:

    • 当抽象方法被调用时,程序应该做什么?
    • 甚至更糟:非空函数应该返回什么?

    应用程序可能不得不崩溃或抛出运行时异常,因此这会导致麻烦。你不能虚拟实现每个抽象函数。

    【讨论】:

      【解决方案3】:

      一个没有抽象方法的类可以简单地声明为抽象类。我想这可以在理论上实例化,但类设计器不希望你这样做。它可能会产生意想不到的后果。

      但通常抽象类有抽象方法。无法实例化它们,原因很简单,就是它们缺少这些方法。

      【讨论】:

      • 是的,可能出现哪些“意外后果”?假设我保证我的对象永远不会调用它的任何函数?
      • 通过声明一个方法,一个类就可以调用该方法。类契约将描述该函数的作用、它需要什么参数以及它返回什么(如果有的话)。抽象类将无法履行该契约。
      • 如果您创建一个方法abstract,您希望它必须由其子类实现,并希望编译器在该方法未被调用时为您捕获错误子类。如果您不需要调用该方法,只需将其省略或提供默认实现即可。
      • @cletus 是的,我完全理解这一点。假设我决定重新设计 C++,以便为抽象类创建对象。有可能吗?
      • @KennyTM 一会儿,忘记逻辑。我想知道的是有没有可能?
      【解决方案4】:

      因为从逻辑上讲它没有任何意义。

      抽象类是不完整的描述。
      它表示需要填写哪些内容才能使其完整,但没有这些部分则不完整。

      我的第一个例子是一个国际象棋游戏:
      游戏有很多不同类型的棋子(国王、王后、典当...等)。

      但是没有实际的piece类型的对象,但是所有的对象都是从piece派生的对象的实例。你怎么能有一个没有完全定义的东西的对象。由于游戏不知道它是如何移动的(即抽象部分),因此创建一个棋子对象是没有意义的。它知道它可以移动,但不知道它是如何移动的。

      【讨论】:

        【解决方案5】:

        抽象类根据定义是不可实例化的。它们要求有派生的具体类。如果抽象类没有纯虚拟(未实现)函数,它还会是什么?

        【讨论】:

          【解决方案6】:

          这与为什么不能更改 const 变量的值、为什么不能从其他类访问私有类成员或为什么不能覆盖 final 方法是同一个问题。

          因为这是这些关键字的目的,以防止您这样做。因为代码的作者认为这样做很危险、不受欢迎或根本不可能,因为一些抽象的原因,比如缺少特定子类需要添加的基本功能。并不是因为类是虚拟的而不能实例化。这是无法实例化一个类将其定义为虚拟的(如果一个无法实例化的类不是虚拟的,这是一个错误。反过来也是如此,如果给定类的实例有意义,它不应该是标记为虚拟)

          【讨论】:

            【解决方案7】:

            为什么我们不能创建抽象类的对象?

            简单的抽象类包含抽象方法(意味着没有主体的函数),我们不能为抽象方法赋予功能。如果我们尝试为抽象方法赋予功能,那么抽象类和虚拟类之间将没有区别。最后,如果我们创建一个抽象类的对象,那么调用无用的函数或抽象方法就没有乐趣了,因为它们没有功能......所以这就是为什么任何语言都不允许我们创建抽象类的对象......

            【讨论】:

            • 您的回答不会对 2010 年接受的答案添加任何内容。
            【解决方案8】:

            实例化的抽象类将毫无用处,因为您会看到更多“调用的纯虚函数”。 :)

            就像:我们都知道一辆汽车会有 3 个踏板、一个方向盘和一个变速杆。现在,如果是这样的话,并且会有 3 个踏板、变速杆和一个轮子的实例,我不买它,我想要一辆汽车,比如有座椅、门、AC 等,实际上有踏板除了存在之外的东西,这就是抽象类没有向我保证的东西,实现它可以做到。

            【讨论】:

              【解决方案9】:

              对象的创建基本上负责为成员变量和成员函数分配内存。但是在这里,在纯虚函数中,我们在派生类中有声明和定义。所以创建对象会产生错误。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-07-05
                • 1970-01-01
                • 1970-01-01
                • 2012-12-20
                • 1970-01-01
                • 2012-04-21
                • 1970-01-01
                • 2011-07-22
                相关资源
                最近更新 更多