所以我目前正在尝试创建自己的enitity组件架构,我遇到了一些问题。
我将我的组件存储为结构,例如
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
我希望我的系统能够存储varing继承的结构。所以我需要使用指针向量。问题是,如何在不知道其原始子类型的情况下将它们动态地转换回原始派生结构?我可以通过没有枚举的代码确定它们的派生类型,因为我想在库中实现它。
我也会接受回答,同时给出实现这个系统的替代方法,记住我想开发它。请注意并提供代码示例,如果可能的话,请提供帮助。
感谢您阅读:)
PS。这是我今天上传的另一个问题的转贴。它作为一个重复的问题被关闭,但副本甚至没有接近回答我的问题。我要求您通过评论与我交谈,而不是阻止其他任何人帮助,从而理解我的问题的正确性。感谢。
答案 0 :(得分:2)
如果您的基类是多态的,您可以使用dynamic_cast
转换回原始版本(有点像Java中的instanceof
):
我们假设您有以下课程:
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;
}
};
然后,您可以将这些值存储在vector
Base*
1 中(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 (Derived1 *d = dynamic_cast<Derived1*>(pbase)) {
d->foo();
}
if (Derived2 *d = dynamic_cast<Derived2*>(pbase)) {
d->bar();
}
}
如果转换失败, dynamic_cast
将返回空指针,因此如果d->foo()
最初为pbase
,您将永远不会调用Derived2*
,因此它是安全的。
请注意,如果Base
不是多态的(请尝试删除virtual
),则不能使用dynamic_cast
(编译器错误)。
1 请注意,您可以(应该)使用智能指针来避免手动释放内存(例如std::shared_ptr
),而不是使用原始指针Base*
。 。如果您这样做,则必须使用dynamic_pointer_cast
代替dynamic_cast
。
答案 1 :(得分:2)
您可以通过RTTI获取有关变量类型的信息:
(typeid(*baseComp) == typeid(ComponentB))
在你的例子中是正确的。
答案 2 :(得分: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";
}
}
输出:
Retrieved component A Retrieved component B Didn't retrieve component C
答案 3 :(得分: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
- 检查它,您会喜欢它。
因为它不依赖于类型和强制转换,而是依赖于简单的int
值,这些值专门用于标识类型。
但在我看来,主要问题完全不同:
在这两个代码中,客户端必须知道所有派生类。在霍尔特的回答中,这是if statements
的部分。在我的回答中,这是switch statement
。
如果有新的派生类Derived3,会发生什么?您需要更改客户端代码。
我相信你可以用多态进行这项工作,只能使用Base
类,而不需要任何强制转换。
有很多这样的例子 - 人物,人(学生,教授),汽车(运动,皮卡,suv's,卡车)。这恰好是我的最爱:
#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
}
在诸如Java
,C#
或PHP
的许多OOP语言中,基类Shape
类称为interface
。请注意它如何定义方法,但不包括任何实现细节。
这允许派生类在不同的编译单元中实现,客户端代码不知道它适用于哪个类。