简体   繁体   中英

C++ cast struct dynamically based on subtype

So I am currently attempting to create my own enitity component architecture and I am having some issues.

I am storing my components as a struct, eg

struct BaseComponent
{
  bool isValid;
}

struct ComponentA : BaseComponent
{
  int someValForA;
}

struct ComponentB : BaseComponent
{
  int someValForB
}

ComponentB* compB = new ComponentB()
compB->someValForB = 10;
BaseComponent* baseComp = compB

ComponentB* comp = (ComponentB*) baseComp

I would like my system to be able to store the structs of varing inheritance. so I would need to use a vector of pointers. The problem is, how do I cast them back to their origional derived struct dynamically without knowing their origional subtype? Can I determine their derived type through code without an enum since I want to implement this in a library.

I will also take answer that also give alternate approaches to implementing this system, keeping in mind that I want to develop it. Please be specfic and give a code example if possible to help.

Thank-you for reading :)

PS. This is a repost of another question I uploaded today. It was closed as a duplicate question, yet the duplicate didn't even come close to answering my question. I request that you understand the exactness of my question by talking to me through comments instead of preventing anyone else from helping. Thanks.

If your base class is polymorphic, you can use dynamic_cast to convert back to the original (a bit like instanceof in Java):

Let's say you have the following classes:

struct Base {
    // We need this or any other virtual member to make Base polymorphic
    virtual ~Base () { }
};

struct Derived1: public Base {
    void foo () {
        std::cout << "foo" << std::endl;
    }
};

struct Derived2: public Base {
    void bar () {
        std::cout << "bar" << std::endl;
    }
};

Then you can store these values in a vector of Base* 1 (the destructor of Base should be virtual in order for this to work properly):

std::vector<Base*> bases;
bases.push_back(new Derived1());
bases.push_back(new Derived2());
bases.push_back(new Derived2());
bases.push_back(new Derived1());

Then you can get back to the derived class using dynamic_cast :

for (auto pbase: bases) {
    if (Derived1 *d = dynamic_cast<Derived1*>(pbase)) {
        d->foo();
    }
    if (Derived2 *d = dynamic_cast<Derived2*>(pbase)) {
        d->bar();
    }
}

dynamic_cast will returns a null pointer if the cast fails, so you will never call d->foo() if pbase was originally Derived2* so it is safe.

Note that if Base is not polymorphic (try removing the virtual ), you cannot use dynamic_cast (compiler error).

1 Note that instead of using a raw pointer Base* , you could (should) use a smart pointer to avoid having to free the memory manually (eg std::shared_ptr ). If you do this, you will have to use dynamic_pointer_cast instead of dynamic_cast .

You can get information about the type of a variable with RTTI like:

 (typeid(*baseComp) == typeid(ComponentB))

which is true in the case of your example.

Rather than throwing out type information, then checking whether every component is the one you're looking after, I suggest you use a container that maps a type to its component (or tells you it doesn't have it).

using TypeId = unsigned int;

namespace detail_typeId {
    TypeId idCounter = 0u;
}

template <class T>
TypeId const idFor = detail_typeId::idCounter++;

This trick uses the side-effect in the initialization of the specializations of idFor<T> to provide a unique identifier value for each type, that can be used as a key. You could also use std::type_index , but that forces you to have polymorphic classes as components. This approach also has the advantage of producing contiguous integer identifiers, which span the range [0, idCounter - 1] .

struct Component {};

A base class for your components.

struct Entity {

    template <class T, class... Args>
    void addComponent(Args &&... args) {
        if(!comps.emplace(
            idFor<T>, std::make_unique<T>(std::forward<Args>(args)...)
        ).second)
            throw std::runtime_error("Component already present.");
    }

    template <class T>
    T *getComponent() {
        auto found = comps.find(idFor<T>);
        return found == end(comps)
            ? nullptr
            : static_cast<T*>(found->second.get());
    }

    std::map<TypeId, std::unique_ptr<Component>> comps;
};

Here we see the actual storage of the components, as well as two convenience functions to access them. The map allows us to retrieve any component based on its type.

Example usage with three user-defined components :

struct CompA : Component { int attribA; };
struct CompB : Component { int attribB; };
struct CompC : Component { int attribC; };

int main() {
    Entity e;
    e.addComponent<CompA>();
    e.addComponent<CompB>();

    if(CompA *c = e.getComponent<CompA>()) {
        std::cout << "Retrieved component A\n";
        c->attribA = 42;
    }

    if(CompB *c = e.getComponent<CompB>()) {
        std::cout << "Retrieved component B\n";
        c->attribB = 42;
    }

    if(CompC *c = e.getComponent<CompC>()) {
        std::cout << "Retrieved component C\n";
        c->attribC = 42;
    } else {
        std::cout << "Didn't retrieve component C\n";
    }
}

Output:

Retrieved component A
Retrieved component B
Didn't retrieve component C

Live on Coliru

Alternatively you can do something like this. It is not as professional as Holt's answer, but it share same disadvantages. More about them later.

#include <iostream>
#include <memory>

struct Base {
       virtual int what() const = 0;
       virtual ~Base(){}
};

struct Derived1 : Base {
       static constexpr int ME = 1;

       int what() const{
              return ME;
       }
};

struct Derived2 : Base {
       static constexpr int ME = 2;

       int what() const{
              return ME;
       }
};

using pBase = std::unique_ptr<Base>;

void doSomething(pBase &base){
       switch(base->what()){
       case Derived1::ME :
              std::cout << "Derived1" << std::endl;
              break;

       case Derived2::ME :
              std::cout << "Derived2" << std::endl;
              break;

       default:
              std::cout << "huh?" << std::endl;

       }
}

int main(){
       pBase base1{ new Derived1() };
       pBase base2{ new Derived2() };

       doSomething(base1);
       doSomething(base2);

       //no need to call delete
}

I did the code using C++11 with smart pointer . If you did not worked with C++11 - check it, you'll love it.

Why this code is less professional than Holt's code?

Because instead of types and casts, it rely on simple int values, that are made specifically for identify the type.

However in my opinion, main question is quite different:

What is the disadvantage of both codes?

In both codes, the client must know all derived classes. In Holt's answer this is the part with if statements . In my answer this is the switch statement .

What will happen if there is new derived class Derived3 ? You need to change the client code.

I believe you can do this with polymorphism and work only with Base class, without any casts.

Example

there are lots of examples like this - figures, people (students, professors), cars (sport, pickups, suv's, trucks). This happens to be my favorite:

#include <iostream>
#include <memory>

struct Shape {
       virtual float area() const = 0;

       virtual const char *name() const = 0;

       virtual ~Shape(){}
};

struct Square : Shape {
       Square(float a) : a(a){

       }

       const char *name() const override{
              return "Quadrat"; // this has nothing to do with class name
       }

       float area() const override{
              return a * a;
       }

private:
       float a;
};

struct Circle : Shape {
       Circle(float r) : r(r){

       }

       const char *name() const override{
              return "Circle";
       }

       float area() const override{
              return PI * r * r;
       }

private:
       constexpr static float PI = 3.14159;

       float r;
};

using pShape = std::unique_ptr<Shape>;

void doSomething(pShape &base){
       std::cout     << base->name()
                     << " has area of "
                     << base->area()
                     << "m2"
                     << std::endl;
}

int main(){
       pShape base1{ new Square(5) };
       pShape base2{ new Circle(5) };

       doSomething(base1);
       doSomething(base2);

       //no need to call delete
}

In many OOP languages such Java , C# or PHP , the base Shape class is called interface . Notice how it define methods, but does not include any implementation details.

This allows derived classes to be implemented in different compile units and client code has no idea what class it works with.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM