【问题标题】:Overriding function in C++ doesn't workC ++中的覆盖函数不起作用
【发布时间】:2011-06-02 05:44:05
【问题描述】:
#include <cstdio>
using namespace std;

class A {
public:
    virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
    virtual void func() { printf("B::func()"); }
};

int main() {
  A a = *(A *)new B();
  a.func();
}

问题很简单:为什么a-&gt;func() 调用类A 中的函数,即使a 包含B 类的对象?

【问题讨论】:

  • 当语法错误得到修复以便编译时,您的代码会做正确的事情(调用B::func())。由于这不是您的实际代码,我不知道您的问题可能是什么!
  • 这段代码可以编译(至少对我来说)。 Visual C++ 2008 速成版
  • @mnm:啊,我看到你已经改变了。实际上,它仍然没有编译(在每个类定义的末尾缺少;)。
  • 另外,a 是一个对象实例,因此不能使用 -> 指针表示法。你应该考虑自动指针。最重要的是,printf() 函数是 C,而不是 C++。
  • 对不起,它不能工作。 a 不是指针类型,并且你没有重新定义它的 -> 操作符,所以你不能在它上面使用 ->。

标签: c++ function inheritance polymorphism overriding


【解决方案1】:
A a = *(A *)new B();
a.func();

这是这段代码中发生的一步一步:

  • new B():在空闲存储区分配了一个 B 类型的新对象,得到了它的地址
  • (A*):对象的地址被强制转换为A*,所以我们有一个A*类型的指针实际上指向了一个B类型的对象,这是有效的。一切正常。
  • A a:问题从这里开始。 A 类型的新本地对象在堆栈上创建并使用复制构造函数A::A(const A&amp;) 构造,第一个参数是之前创建的对象。
  • 指向 B 类型原始对象的指针在此语句后丢失,导致内存泄漏,因为它是在使用 new 的空闲存储上分配的。
  • a.func() - 在类 A 的(本地)对象上调用该方法。

如果您将代码更改为:

A& a = *( A*) new B();
a.func();

那么只会构造一个对象,它的指针将被转换为A*类型的指针,然后解除引用,并用这个地址初始化一个新的引用。虚函数的调用会被动态解析为B::func()


但请记住,您仍然需要释放该对象,因为它是使用 new 分配的:

delete &a;

顺便说一句,只有当 A 有一个虚拟析构函数时才会正确,这需要 B::~B() (幸运的是,这里是空的,但在一般情况下不需要)也会被调用。如果 A 没有虚拟析构函数,那么您需要通过以下方式释放它:

delete (B*)&a;

如果您想使用指针,则与引用相同。代码:

A* a = new B(); // actually you don't need an explicit cast here.
a->func();
delete (B*)a; // or just delete a; if A has a virtual destructor.

【讨论】:

  • +1 得到很好解释的答案。 :) 只是一个旁注:许多人更喜欢将“免费存储”称为“堆”。
  • @Kos:我要强调的是,delete &amp;a;delete (B*)&amp;a;delete (B*)a; 只是让它工作的技巧,而在实践中应该真正避免这种情况,并使用指针和虚拟析构函数。
  • @Venemo,见stackoverflow.com/questions/1350819/c-free-store-vs-heap - 我不确定,但“免费商店”似乎更像是“C++ 标准术语”,而且可能是正确的 :)
  • @7vies,真的!我确实专注于“如何让它在技术上正确”,而不是“如何让它看起来不错”......但是,C++ 中内存管理、智能指针等的良好实践是一个广泛的话题,所以我只是避免推荐虚拟析构函数......哦,等等,是吗?我在此推荐一个虚拟析构函数! :)
  • @Kos:这不是要涵盖更广泛的主题,您只是没有明确表示实际上不应该使用您在技术上正确的技巧。就像“这会起作用,但你永远不应该这样做,而是这样做 that”。否则有些人会从你那里学到坏东西。
【解决方案2】:

现在您已经修改了代码 sn-p,问题就很清楚了。多态性(即虚函数)只能通过指针和引用调用。这些你都没有。 A a = XXX 不包含B 类型的对象,它包含A 类型的对象。通过进行指针转换和取消引用,您已经“切掉”了对象的 B-ness。

如果您执行A *a = new B();,那么您将获得预期的行为。

【讨论】:

  • 谢谢。这有帮助。编译器至少应该给出警告或什么。我多么讨厌 C++,希望我可以使用 C#。
  • @mnm:为什么要发出警告?据它所知,这正是你的本意!
  • 这不是一个很好的解释 IMO - 它没有提到这里创建了 两个 对象并且存在内存泄漏。没有像“切片”这样的 C++ 术语(嗯,有,但它比技术更概念化),这里发生的是“复制构造”。
  • @Kos:是的,因此是吓人的引语! “切片”是一个术语,例如迈耶斯用来指代这种效果。是的,您是正确的,在原始代码中,创建了两个对象,并且存在内存泄漏。但在我看来,“切片”(或任何你想称之为的)是导致 OP 混乱的概念。
  • 不,切片 - 正如我从此处链接的描述中所理解的那样 - 是早期链接 operator= 及其原因的一个花哨的名称。 OP 的问题似乎是缺乏理解 C++ 中的“值类型”是什么以及它对对象的行为方式,因为 - AFAIK - C# 中没有类似的概念(只有引用(与 C++ 引用不同)和 - 在不安全的部分 - 指针,对吧?)。 Mnn 试图给一个值赋值,就好像它是一个类似 C# 的引用,最重要的是向他解释那里实际发生了什么。
【解决方案3】:

你遇到的问题很经典object slicing

A a = *(A *)new B();

使a 成为A 的引用或指针,虚拟调度将按您的预期工作。更多解释请见this other question


您评论了另一个答案“编译器应该至少给出警告或什么”。这就是为什么让基类成为不可复制的抽象类被认为是一种好习惯的原因:您的初始代码一开始就不会编译。

【讨论】:

  • 不错的链接,但恐怕这个问题与“切片”完全无关。这里的问题是 OP 不小心创建了另一个对象。
  • @Kos: ...并在此过程中对从中复制的对象进行切片。无论如何,至少你证明你的反对票是合理的。
  • 我会尽力解释。 :) 我不是冒犯或什么的。只是这里不会发生切片。 A 类的对象是用A::A(const A&amp;) 显式创建和构造的。据我了解,切片发生在我们想要B::operator=,在 B 类的对象上,而是使用A::operator= 并将对象分配给“部分”。在这里,A 类的对象请求(无意中!)并完全构造。问题不在于调用错误的 op= 或错误的复制 ctor,而是发生了不希望的复制构造这一事实。因此 - 不切片 IMO。
  • @Kos:切片通常在函数参数传递的上下文中演示,其中不涉及operator=,仅涉及复制构造函数。
  • @Kos:我同意@icecrime。切片通常是指 any 复制——通过复制构造函数或复制赋值运算符——其中源类型是比目标类型更派生的类型。这通常但不一定是不可取的。
【解决方案4】:

这可能会做到这一点。

A &a = *(A *)new B();
a.func();

或者

A *a = new B();
a->func();

【讨论】:

  • 这也有效,但我认为以 Oli Charlesworth 所描述的方式使用指针似乎更“干净”。
  • 在 C++ 中,*pointer 就像一个引用(如果不完全是一个引用),它不是一个值。所以 *pointer_to_A 是 A&。
【解决方案5】:

虚拟分派仅适用于指针或引用类型:

#include <cstdio>
using namespace std;

class A {
public:
  virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
  virtual void func() { printf("B::func()"); }
};

int main() {
  A* a = new B();
  a->func();
}

【讨论】:

  • “仅适用于指针或引用类型”- 是的,但这并不是真的“不适用于值类型”,而是“对值类型没有多大意义”。如果我们知道一个对象恰好是 A 类型(它适用于值类型),那么我们总是可以在编译时正确地推断出该方法,因此不需要后期链接。
【解决方案6】:

问题是使用 A a = *(A *)new B(); 将 B 转换为 A

您可以通过删除 *(A *) 将其更改为 (A *a = new B(); ) 来修复它,但我会更进一步,因为您的变量名不适合 B 的实例化。

应该是

B *b = new B(); 
b->func(); 

【讨论】:

    【解决方案7】:

    因为您在将动态分配的对象复制到 A 类型的对象 a 时执行了切片(这也导致了内存泄漏)。

    a 应该是一个引用 (A&amp;),或者只保留指针。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-18
      • 1970-01-01
      相关资源
      最近更新 更多