1

I have two classes, Object and Ball. Ball is derived from Object. Object has a virtual function "move" and a non virtual function "moveFast" that calls move. Class Ball redefines the move function from it's parent class.

#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();
}

The List class takes in Objects and calls their moveFast function. Because a,b, and c are just Objects I would expect the first 3 lines of output to be "Moving 100". d however, is an instance of the Ball class. So I would expect the 4th line of output to say "Rolling 100", because Ball redefined the move function.

Right now all the output prints

Moving 100
Moving 100
Moving 100
Moving 100

Is there a way to get Ball's definition of move called from List?

  • Compiler and version please. Also, please try the following complete program (using a single .cpp file preferably, to eliminate other source of errors): http://coliru.stacked-crooked.com/a/346dc7763c73e410 – dyp Oct 11 '14 at 22:55
  • You do not need to create objects in C++ via `new`. In this case, `Ball b; foo(&b);` is sufficient. – dyp Oct 11 '14 at 22:56
  • Are you calling this from in a constructor? If so, see http://stackoverflow.com/questions/496440/c-virtual-function-from-constructor – o11c Oct 11 '14 at 23:02
  • This is not an actual program. Just an example of the concept I am asking about. Class A as a virtual member function c and a non virtual function d. function d calls c. Then Class B, derived from A, redefines virtual function c. Now when calling function d from an instance of class B, how can I make function d call Class B's definition of c. –  Oct 11 '14 at 23:05
  • 3
    *"This is not an actual program."* And that's the problem: The program/code you show here does not reproduce the problem, it probably does not contain the same issue as the code you're actually using. One could guess that your real code uses `void foo(Object o);` and suffers from the slicing problem. – dyp Oct 11 '14 at 23:09
  • @user3543433 `This is not an actual program.` The program that *dyp* created at the link *is* an actual program, and works as expected. So post a real program and not a "concept of a program" that duplicates your issue. – PaulMcKenzie Oct 11 '14 at 23:23
  • You were correct. My problem was not coming from where I thought it was. I should have made a small test program and tested it before posting. I have recreated the problem and will update the original question. –  Oct 11 '14 at 23:37
  • My guess would be that you have different arguments to your member function, which makes it "not work", because your derived class doesn't actually implement the virtual function (in C++11 you can avoid that particular problem by using `override` keyword when you declare the function inside the derived class) – Mats Petersson Oct 11 '14 at 23:37
  • Now you have slicing, like @dyp guessed. – Deduplicator Oct 11 '14 at 23:54
  • dyp & Dedublicator is definitely right, I was guessing wrong... – Mats Petersson Oct 11 '14 at 23:55
  • How would I implement something like this without slicing? Should I be using a different data structure? And to make sure I'm understanding correctly, the problem is stemming from the `addObj(Object o)` line correct? –  Oct 12 '14 at 00:06
  • @user3543433 Good lesson here on your ["Minimal, Complete, Verifiable Example"](http://stackoverflow.com/help/mcve). It was a good instinct to want to cut a larger program and make it *minimal*, but when you do that...don't forget the *verifiable* part! To avoid slicing you need to be sure you are going through a base class *pointer* not a base class *instance*. When you say **Object ele;** then that really literally means that it is an Object--with memory allocated to the size of an Object. Read the slicing post fully, and also [smart pointers](http://stackoverflow.com/questions/106508/). – HostileFork says dont trust SE Oct 12 '14 at 00:07
  • @HostileFork Thanks for the explanation and the additional links. I now understand the problem and was able to fix it. –  Oct 12 '14 at 00:24

2 Answers2

0

The problem is that you store your Objects in the list by value. virtual functions will only work on pointers. the moment you try to add an object to the list through list::void addObj(Object o). The argument is passed by value. This means that it is copied and if you copy a base class only the base class functionality will be copied it's called the slicing problem (like dyp mentioned). you should change your nodes to hold a pointer to the object and redo your add object function to take a pointer to the element to prevent copying and slicing.

like this

 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;
}

which outputs:

Moving 100
Moving 100
Moving 100
Rolling 100
Liam Clark
  • 106
  • 1
  • 2
  • 8
  • Thanks for writing this out! I wish I had seen it before I started my answer. It basically just restates this. –  Oct 12 '14 at 00:38
  • Also don't forget to add virtual destructors to the baseClasses otherwise you will get memory leaks. – Liam Clark Oct 12 '14 at 11:23
0

Like many have said, there was a slicing problem in List.

List::Node stored an actual Object, so when an instance of Ball was passed into addObj(Object o), the additional functionality of Ball was "sliced", and only the parts stored by it's base class "Object" remained.

Changing the Node class to store an Object pointer instead of an Object instance fixed this problem. This change also requires the addObj function to be altered to take in a pointer. The List class now looks like this:

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;
        }
    }
};

changing int main() to provide the altered inputs results in the expected output.

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();
}