3

I have the following problem:

class Component
{
public:
virtual void update(){};
};


class TestComponent : public Component
{
    void update()override;
};


class GameObject
{
public :
void addComponent(Component& comp)
{
 std::shared_ptr<Component> test = std::make_shared<Component>(comp);
 components.push_back(test);
}
void GameObject::update()
{
 for(auto comp : components)
 {
 //I want to call the derived update here without casting it to the derived class if possible
 comp->update();
 }
}
private:
    std::vector<std::shared_ptr<Component>> components;
};

Somewhere else in my code:

GameObject go;
TestComponent comp;
go.addComponent(comp);

I would just assume that when I add an object to the vector of Components that I can simply call update on all of the vectors elements and it uses the overridden update of the object I passed into addComponent. So for my example above I expect the forloop to call the update of the TestComponent I added and not the baseclass update. But thats not whats happening so I assume I am missing something. Or maybe my approach is just wrong in general. I am not really sure about my usage of a sharedpointer for this? Any hints in the right direction would be appreciated.

Wuceng
  • 53
  • 3
  • 3
    Possible duplicate of [What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – TypeIA Nov 17 '18 at 00:31

2 Answers2

7

There are no TestComponent objects in your vector. They are all Components.

void addComponent(Component& comp)
{
    std::shared_ptr<Component> test = std::make_shared<Component>(comp);
    components.push_back(test);
}

In this function, you create a new Component object that s a copy of the Component sub-object of the TestComponent object you passed in. This is known as object slicing.

You will need to either avoid copying the objects or implement some sort of cloneable interface.

To avoid copying the object, you can do something like this:

class GameObject
{
public:
    void addComponent(std::shared_ptr<Component> comp)
    {
        components.push_back(comp);
    }
    // ...
};

int main() {
    GameObject go;
    std::shared_ptr<TestComponent> testComponent = std::make_shared<TestComponent>();
    go.addComponent(testComponent);
}

In this case, main and go share ownership of a single TestComponent object. If you want to avoid that, you could implement a clonable interface so that objects know how to copy themselves:

class Component
{
public:
    virtual void update(){};
    virtual std::shared_ptr<Component> clone() const
    {
        return std::make_shared<Component>(*this);
    }
};


class TestComponent : public Component
{
    void update() override;
    std::shared_ptr<Component> clone() const override
    {
        return std::make_shared<TestComponent>(*this);
    }
};

class GameObject
{
public:
    void addComponent(const Component& comp)
    {
        components.push_back(comp.clone());
    }
    // ...
};

int main()
{
    GameObject go;
    TestComponent comp;
    go.addComponent(comp);
}

In this case, you still make a copy, but every class has to override the clone method.


As for the question about shared_ptr: std::shared_ptr is a smart pointer that shares ownership of an object between multiple owners. An object owned by one or more std::shared_ptrs is only destroyed when all of the std::shared_ptr objects sharing ownership of it are destroyed. If you don't need this behavior, then std::unique_ptr exists and will be somewhat more performant. std::unique_ptr models unique ownership. Only one std::unique_ptr object can ever reference an object at a time, and the object is destroyed when that std::unique_ptr is destroyed.

Either type of smart pointer could be used in this situation:

  • Use std::shared_ptr if you want a GameObject to be able to share ownership of its components with other owners (perhaps other GameObjects).
  • Use std::unique_ptr if you want a GameObject to have exclusive ownership of its components. In this case the GameObject could still allow other objects to access its components, but the components' lifetimes would be tied to the lifetime of the GameObject
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Beat me too the punch, however I think you did not emphasize how the second solution ( aka the copy ) defeats the purpose of using a shared_ptr to completely answer the question. – Chris Mc Nov 17 '18 at 00:53
  • 1
    @ChrisMc That depends on what operations exist on the `GameObject` class. In this limited example you're right, `unique_ptr` would be a better option, but it may be that OP wants other objects to be able to retrieve and share ownership of the components owned by the `GameObject`, but for some reason still wants to always copy the component when it's added. – Miles Budnek Nov 17 '18 at 00:57
  • @Galik the `auto comp` is a copy of the shared_ptr not the `TestComponent`, so there is no slicing there. – Chris Mc Nov 17 '18 at 00:57
  • no disagreement there. It is the second to last line of the question "I am not really sure about my usage of a sharedpointer for this?" is all I'm _suggesting_ you address. =) – Chris Mc Nov 17 '18 at 01:03
  • 1
    @ChrisMc I didn't even see that sentence. Added a bit about that. – Miles Budnek Nov 17 '18 at 01:14
  • Thank you! I knew about object slicing but I thought that passing by reference would avoid that. Also I tried the craziest things but never thought about the simple thing of just passing over a shared_ptr of the component... But one more question. I also thought that I could solve it by using templates. What would be the better approach here, templates or passing in a sharedpointer? – Wuceng Nov 17 '18 at 07:51
1

To make your code compile just add another method, rest are fine . Since update method is virtual and the base class is non-abstract, both can call update without any issue.

void TestComponent::addComponent(const TestComponent & tcomp)
{
    std::shared_ptr<Component> test = std::make_shared<TestComponent >(tcomp);
    components.push_back(test);
}

Edited: For adding any component, derived or base class, use this way:

void TestComponent::addComponent(std::shared_ptr<Component> comp)
{
    components.push_back(comp);
}
seccpur
  • 4,996
  • 2
  • 13
  • 21
  • That may work for Testcomponent but I expect to have dozens of different Components and it would be kinda overkill to add a method for every single component. But actually I could in fact make use of templates, right? – Wuceng Nov 17 '18 at 07:47
  • @Wuceng: Edited just now for adding different components. – seccpur Nov 17 '18 at 07:51