【问题标题】:Virtual function call from inside a base class function [duplicate]从基类函数内部调用虚函数[重复]
【发布时间】:2014-10-11 22:50:10
【问题描述】:

我有两个类,Object 和 Ball。 Ball派生自Object。对象有一个虚函数“move”和一个调用move的非虚函数“moveFast”。 Ball 类从它的父类重新定义了 move 函数。

#include <iostream>

struct Object
{
    virtual void move(int dist)
    {
        std::cout<<"Moving "<<dist<<std::endl;
    }
    void moveFast(int multiplier) 
    {
        move(10*multiplier);
    }
};

struct Ball : public Object
{
    void move(int dist)
    {
        std::cout<<"Rolling "<<dist<<std::endl;
    }
};

class List
{
    struct Node
    {
        Node* next;
        Object ele;
        Node(Object e, Node* n=NULL) : ele(e), next(n){}
    };

    Node* head;
public:
    List() : head(NULL){}
    void addObj(Object o)
    {
        if(head==NULL)
        {
            head =  new Node(o);
            return;
        }

        Node* current = head;
        while(current->next!=NULL)
        {
            current=current->next;
        }
        Node* obj = new Node(o);
        current->next=obj;
    }
    void doStuff()
    {
        Node* current = head;
        while(current!= NULL)
        {
            current->ele.moveFast(10);
            current=current->next;
        }
    }
};

int main()
{
    Object a,b,c;
    Ball d;
    List list;

    list.addObj(a);
    list.addObj(b);
    list.addObj(c);
    list.addObj(d);
    list.doStuff();
}

List 类接收对象并调用它们的 moveFast 函数。因为 a、b 和 c 只是对象,我希望前 3 行输出是“移动 100”。 然而,d 是 Ball 类的一个实例。所以我希望输出的第 4 行说“Rolling 100”,因为 Ball 重新定义了 move 函数。

现在所有的输出打印

Moving 100
Moving 100
Moving 100
Moving 100

有没有办法从 List 中获取 Ball 的移动定义?

【问题讨论】:

  • 请编译器和版本。另外,请尝试以下完整程序(最好使用单个 .cpp 文件,以消除其他错误来源):coliru.stacked-crooked.com/a/346dc7763c73e410
  • 您不需要通过new 在 C++ 中创建对象。在这种情况下,Ball b; foo(&amp;b); 就足够了。
  • 你是在构造函数中调用它吗?如果是这样,请参阅stackoverflow.com/questions/496440/…
  • 这不是一个实际的程序。只是我要问的概念的一个例子。类 A 作为一个虚成员函数 c 和一个非虚函数 d。函数 d 调用 c。然后从 A 派生的类 B 重新定义了虚函数 c。现在从 B 类的实例调用函数 d 时,如何让函数 d 调用 B 类的 c 定义。
  • “这不是一个实际的程序。” 这就是问题所在:您在此处显示的程序/代码不会重现该问题,它可能不包含相同的问题作为您实际使用的代码。可能会猜到您的真实代码使用了void foo(Object o); 并且存在切片问题。

标签: c++ polymorphism virtual


【解决方案1】:

问题是您将对象按值存储在列表中。虚函数只对指针起作用。当您尝试通过 list::void addObj(Object o) 将对象添加到列表中时。参数按值传递。这意味着它被复制了,如果您复制一个基类,则只会复制基类功能,这称为切片问题(如提到的 dyp)。您应该更改节点以保存指向对象的指针并重做添加对象函数以获取指向元素的指针以防止复制和切片。

喜欢这个

 class List
{
    struct Node
    {
        Node* next;
        Object* ele;
        Node(Object* e, Node* n=nullptr) : ele(e), next(n){}
    };

    Node* head;
public:
    List() : head(nullptr){}
    void addObj(Object* o)
    {
        if(head==nullptr)
        {
            head =  new Node(o);
            return;
        }

        Node* current = head;
        while(current->next!=nullptr)
        {
            current=current->next;
        }
        Node* obj = new Node(o);
        current->next=obj;
    }
    void doStuff()
    {
        Node* current = head;
        while(current!= nullptr)
        {
            current->ele->moveFast(10);
            current=current->next;
        }
    }
};

int main()
{
    Object a,b,c;
    Ball d;
    List list;

    list.addObj(&a);
    list.addObj(&b);
    list.addObj(&c);
    list.addObj(&d);
    list.doStuff();
    return 0;
}

哪个输出:

Moving 100
Moving 100
Moving 100
Rolling 100

【讨论】:

  • 感谢您写下这篇文章!我希望我在开始回答之前已经看到它。它基本上只是重申了这一点。
  • 另外不要忘记在基类中添加虚拟析构函数,否则会导致内存泄漏。
【解决方案2】:

正如许多人所说,List 中存在切片问题。

List::Node 存储了一个实际的 Object,因此当 Ball 的实例被传递到 addObj(Object o) 时,Ball 的附加功能被“切片”,只剩下它的基类“Object”存储的部分。

将 Node 类更改为存储 Object 指针而不是 Object 实例解决了这个问题。此更改还需要更改 addObj 函数以接收指针。 List 类现在看起来像这样:

class List
{
    struct Node
    {
        Node* next;
        Object* ele;//<-- This is the Big change
        Node(Object* e, Node* n=NULL) : ele(e), next(n){}
    };

    Node* head;
public:
    List() : head(NULL){}
    void addObj(Object* o)
    {
        if(head==NULL)
        {
            head =  new Node(o);
            return;
        }

        Node* current = head;
        while(current->next!=NULL)
        {
            current=current->next;
        }
        Node* obj = new Node(o);
        current->next=obj;
    }
    void doStuff()
    {
        Node* current = head;
        while(current!= NULL)
        {
            current->ele->moveFast(10);
            current=current->next;
        }
    }
};

更改 int main() 以提供更改后的输入会产生预期的输出。

int main()
{
    Object *a,*b,*c;
    b=c=a=new Object();
    Ball* d = new Ball();
    List list;

    list.addObj(a);
    list.addObj(b);
    list.addObj(c);
    list.addObj(d);
    list.doStuff();
}

【讨论】:

    猜你喜欢
    • 2010-09-28
    • 2018-02-08
    • 2011-02-18
    • 2021-05-09
    • 2012-01-27
    • 2023-04-03
    • 1970-01-01
    相关资源
    最近更新 更多