C++ 基于子类型动态转换结构

问题描述 投票:0回答:4

所以我目前正在尝试创建自己的实体组件架构,但遇到了一些问题。

我将组件存储为结构,例如

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

我希望我的系统能够存储可变继承的结构。所以我需要使用指针向量。问题是,如何在不知道其原始子类型的情况下将它们动态地转换回其原始派生结构?我可以通过没有枚举的代码来确定它们的派生类型吗,因为我想在库中实现它。

我还将接受也给出实现该系统的替代方法的答案,请记住我想开发它。请具体说明并提供代码示例(如果可能的话)以提供帮助。

感谢您的阅读:)

PS。这是我今天上传的另一个问题的转发。它作为重复问题被关闭,但重复问题甚至没有接近回答我的问题。我请求您通过评论与我交谈来理解我问题的准确性,而不是阻止其他人提供帮助。谢谢。

c++ struct casting
4个回答
6
投票

如果你的基类是多态的,你可以使用

dynamic_cast
转换回原来的(有点像 Java 中的
instanceof
):

假设您有以下课程:

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

struct Derived1: Base {
    void foo () {
        std::cout << "foo\n";
    }
};

struct Derived2: Base {
    void bar () {
        std::cout << "bar\n";
    }
};

然后您可以将这些值存储在

vector
1
Base* 中(
Base
的析构函数应该是
virtual
才能正常工作):

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

然后你可以使用

dynamic_cast
:

返回派生类
for (auto *pbase: bases) {
    if (auto *d = dynamic_cast<Derived1*>(pbase)) {
        d->foo();
    }
    if (auto *d = dynamic_cast<Derived2*>(pbase)) {
        d->bar();
    }
}
如果转换失败,

dynamic_cast
将返回空指针,因此如果
d->foo()
最初是
pbase
,那么你永远不会调用
Derived2*
,所以它是安全的。

请注意,如果

Base
不是多态的(尝试删除
virtual
),则无法使用
dynamic_cast
(编译器错误)。

1 请注意,您可以(应该)使用智能指针来避免手动释放内存(例如

Base*
),而不是使用原始指针
std::shared_ptr
。如果您这样做,则必须使用
dynamic_pointer_cast
而不是
dynamic_cast


2
投票

我建议您使用一个将类型映射到其组件(或告诉您它没有类型)的容器,而不是抛出类型信息,然后检查每个组件是否是您正在寻找的组件。

using TypeId = unsigned int;

namespace detail_typeId {
    TypeId idCounter = 0u;
}

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

这个技巧利用

idFor<T>
特化初始化中的副作用为每种类型提供一个唯一的标识符值,可以用作键。您还可以使用
std::type_index
,但这迫使您将多态类作为组件。这种方法还具有生成连续整数标识符的优点,其跨越范围
[0, idCounter - 1]

struct Component {};

组件的基类。

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

在这里我们看到组件的实际存储,以及两个访问它们的便捷函数。该映射允许我们根据其类型检索任何组件。

三个用户定义组件的使用示例:

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

输出:

回收A组份
检索到的组件 B
没有检索到组件 C

Coliru 直播


2
投票

您可以使用 RTTI 获取有关变量类型的信息,例如:

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

对于你的例子来说这是正确的。


0
投票

或者你也可以这样做。它不像霍尔特的答案那么专业,但也有同样的缺点。稍后会详细介绍它们。

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

我使用

C++11
smart pointer
编写了代码。如果您没有使用过
C++11
- 检查一下,您会喜欢它的。

为什么这段代码不如Holt的代码专业?

因为它依赖于简单的

int
值,而不是类型和强制转换,这些值专门用于识别类型。

但是在我看来,主要问题是完全不同的:

这两种代码的缺点是什么?

在这两个代码中,客户端必须知道所有派生类。在霍尔特的回答中,这是带有

if statements
的部分。在我的回答中,这是
switch statement

如果有新的派生类 Derived3 会发生什么?您需要更改客户端代码。

我相信你可以通过多态性来做到这一点,并且只使用

Base
类,而不需要任何强制转换。

示例

有很多这样的例子 - 人物、人(学生、教授)、汽车(运动车、皮卡、越野车、卡车)。这恰好是我的最爱:

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

在许多 OOP 语言中,例如

Java
C#
PHP
,基
Shape
类称为
interface
。请注意它如何定义方法,但不包含任何实现细节。

这允许派生类在不同的编译单元中实现,并且客户端代码不知道它与哪个类一起工作。

© www.soinside.com 2019 - 2024. All rights reserved.