【发布时间】:2020-01-19 04:10:23
【问题描述】:
我有一个 C++ 项目,使用 OpenFrameworks 进行渲染,它在窗口中反弹了几个球(圆圈)。它们是使用组件模式(有时称为策略模式)实现的,以分别封装外观和行为。这使得外观(此处:Amiga Ball)和行为(此处:重力弹跳)在运行时可互换。
模式本身的灵感来自Robert Nystrom's book Game Programming Patterns 中的组件模式章节。 在按下一个键时,“重力弹跳”的行为与“无重力弹跳”交换(这可能是我在编程而不是设计的原因)。 我想使用智能指针来实现该模式(在这种情况下,unique_ptr 会这样做,因为没有共享,并且一个组件对象始终由 GameObject 唯一持有)。 我的代码中有几个“丑陋”的部分与 unique_ptr 的使用以及我对组件使用多态性但似乎有时不得不将它们视为派生类对象而不是基类的事实有关。
我想知道它是否是一个好的和可行的设计和实施 w.r.t.该模式的总体设计和实现,尤其是智能指针的使用。
注意:我不关心这里的 const 正确性,尽管我应该关心。所以我敢肯定有很多 const 可以放在那里,但请不要仅仅因为这个而抨击我,除非它会影响其他问题。
所以这里...
我们有一个 GameObject 类,其中包含屏幕上任何对象应具有的最少信息: GameObject.hpp:
class GameObject {
public:
float x;
float y;
GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics);
GameObject();
GameObject(float x, float y);
void update();
void draw();
void setPhysicsComponent(unique_ptr<PhysicsComponent> pc);
void setGraphicsComponent(unique_ptr<GraphicsComponent> pc);
unique_ptr<PhysicsComponent>& getPhysicsComponent();
unique_ptr<GraphicsComponent>& getGraphicsComponent();
private:
unique_ptr<PhysicsComponent> physics_;
unique_ptr<GraphicsComponent> graphics_;
};
GameObject.cpp:
GameObject::GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics) : physics_(move(physics)), graphics_(move(graphics)) {}
GameObject::GameObject() {}
GameObject::GameObject(float x, float y) : x(x), y(y) {}
void GameObject::update(){
physics_->update(*this);
}
void GameObject::draw() {
graphics_->draw(*this);
}
void GameObject::setPhysicsComponent(unique_ptr<PhysicsComponent> pc) {
physics_ = std::move(pc);
}
void GameObject::setGraphicsComponent(unique_ptr<GraphicsComponent> gc) {
graphics_ = std::move(gc);
}
std::unique_ptr<GraphicsComponent>& GameObject::getGraphicsComponent() {
return graphics_;
}
std::unique_ptr<PhysicsComponent>& GameObject::getPhysicsComponent() {
return physics_;
}
然后是组件的基类(在这种情况下,让我们关注物理组件,因为图形组件是等价的):
class GameObject;
class PhysicsComponent {
public:
virtual ~PhysicsComponent() {std::cout<<"~PhysicsComponent()"<<std::endl;}
virtual void update(GameObject& obj) = 0;
void setXSpeed(float xs) {xSpeed = xs;};
float getXSpeed() {return xSpeed;};
void setYSpeed(float ys) {ySpeed = ys;};
float getYSpeed() {return ySpeed;};
protected:
float ySpeed;
float xSpeed;
};
PhysicsComponent 的具体实现如下所示:
class GravityBouncePhysicsComponent : public PhysicsComponent {
public:
virtual ~GravityBouncePhysicsComponent() {std::cout<<"~GravityBouncePhysicsComponent()"<<std::endl;};
GravityBouncePhysicsComponent();
GravityBouncePhysicsComponent(float radius, float xSpeed, float ySpeed, float yAccel);
virtual void update(GameObject& obj);
void setRadius(float r) {radius = r;};
float getRadius() {return radius;};
private:
// Physics
float yAcceleration;
float radius;
};
在 GravityBouncePhysicsComponent.cpp 中,空构造函数创建了这样一个组件,其中 xSpeed 和 ySpeed(位于基类中)和 yAcceleration(仅在派生组件类中有意义)的随机值。 然后还有一个叫做 FloatBouncePhysicsComponent 的代码类似。
现在我想在主程序中使用这些类,恰好是 OpenFrameworks 中的 ofApp 类。头文件是这样的(省略了一些无关紧要的东西):
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
void keyPressed(int key);
// ... lots of other event functions for mouse etc.
private:
std::vector<GameObject> balls;
GameObject createAmigaBall(float x, float y);
// more variables and some methods here ...
};
现在在 ofApp.cpp 文件中它开始变得丑陋。例如,在创建新球的辅助函数中,它看起来像这样:
GameObject ofApp::createAmigaBall(float x, float y) {
GameObject go(x,y); // create an empty GameObject on the stack at position x,y
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
if(gravity) {
go.setPhysicsComponent(std::make_unique<GravityBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<GravityBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
} else {
go.setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<FloatBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
}
return go; // calls the copy constructor for return value, so we have no dangling pointers or refs
}
这里 AmigaBallGraphicsComponent 的空构造函数生成一个随机半径。这个半径属于这个派生类,因为它肯定不在每个 GraphicsComponent 中。 然而,这导致不得不笨拙地从生成的具有丑陋强制转换的组件中提取半径并访问原始指针,只是为了在 GravityBouncePhysicsComponent 中设置相同的半径而再次这样做。
如果你觉得这很丑,看看这个 sn-p:
void ofApp::keyPressed(int key){
if(key == 'a') {
if(gravity) {
for(int i=0; i<balls.size(); i++) {
unique_ptr<GravityBouncePhysicsComponent> opc (static_cast<GravityBouncePhysicsComponent*>((balls[i].getPhysicsComponent()).release()));
float r = opc->getRadius();
float xs = opc->getXSpeed();
float ys = opc->getYSpeed();
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
}
} else {
// similar code for the non-gravity case omitted ...
}
}
gravity =!gravity;
}
}
而且,好像强制转换为派生类的东西还不够,结果证明这段代码不起作用
auto gc = std::make_unique<AmigaBallGraphicsComponent>();
go.setGraphicsComponent(gc);
而这个。什么...?
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
这不应该完全一样吗?
提前感谢您对整个混乱情况的任何见解(或者是吗?)。
【问题讨论】:
-
请发布您的编译器向您报告的实际错误。
-
go.setGraphicsComponent(std::move(gc));。std::unique_ptr的全部意义在于它只能移动以确保唯一的所有权。 -
没有错误。该代码有效。我的问题清楚地询问该模式的一般设计和实现是否在 C++ 中做得很好,以及我使用智能指针是否可行。
-
在 getter 中返回对
unique_ptr的引用没有多大意义。如果 getter 的目的是授予对对象的读写访问权限,同时保留对象的所有权,则只需返回一个引用。 -
@Martin 我正在回答你最底层的问题:你最后两个 sn-ps 不相同,第一个尝试复制
std::unique_ptr而不是移动它,这就是它不起作用的原因。
标签: c++ polymorphism smart-pointers unique-ptr strategy-pattern