C ++ 11:为派生类设置成员变量时避免向下转换

时间:2017-04-14 19:29:04

标签: c++11 polymorphism class-design downcast

我想说我想构建一个Car,它包含MotorTire等组件,这些组件都来自"组件&#34 ;基类。每个部件都有自己的状态(即电机具有RPM,轮胎具有压力等)作为子类。 Car类存储所有组件类。

现在我希望Car课程有一个"保存汽车状态"返回一个循环遍历所有组件的向量的对象的函数,并保存所有组件的所有状态以便稍后恢复它们。每个组件应单独负责将其状态存储和恢复到汽车状态对象。

下面的代码是我想出的最小例子。但是它在代码中至少有两个丑陋的部分,我认为应该避免这些部分:

  • 首先,当我想将汽车组件设置回先前保存的状态时,我需要向下转换基类状态。这是一个有效的转发情况吗?我读到这通常是设计糟糕的表现。那么,我该如何改进呢?我是否完全滥用了多态性的概念?

  • 其次,我需要获取unique_ptr的原始指针,将状态传递回组件并在那里读取。对我来说似乎也很丑陋。 感谢您的评论!

这是我的代码:

#include <iostream>
#include <vector>

class StateClass{
};

class component{
public:
  virtual std::unique_ptr<StateClass> saveStates() = 0;
  virtual void loadStates(StateClass* states) = 0;
};

class Motor:public component
{
public:
  Motor(){
    mstates.rpm = 6000;
    mstates.motorstates::oilLevel = 1.0;
  }
  struct motorstates: public StateClass{
    double rpm;
    double oilLevel;
    void setStates(){
    }
  };

  std::unique_ptr<StateClass> saveStates(){
    std::unique_ptr<StateClass> tmp(new motorstates(mstates));
    return tmp;
  };

  void loadStates(StateClass* states){
    motorstates* savedState = static_cast<motorstates*>(states); // <=== should this be avoided??
    mstates.rpm = savedState->rpm;
    mstates.oilLevel = savedState->oilLevel;
  };

// private:
  void someMethod1();
  void someMethod2();
  motorstates mstates;
};

class Car{
public:
  Car(){
    listOfComponents.push_back(&amotor);
  }
  std::vector<component*> listOfComponents;

// private:
  Motor amotor;

  std::vector<std::unique_ptr<StateClass>> saveState(){
    std::vector<std::unique_ptr<StateClass> > states;
    for(auto comp : listOfComponents){
      states.push_back(comp->saveStates());
    }
    return states;
  }

  void loadState(std::vector<std::unique_ptr<StateClass>>& savedStates){
    int cntstates = 0;
    for(auto comp: listOfComponents){
      comp->loadStates(savedStates.at(cntstates++).get());  // <=== this seems pretty ugly
    };
  }
};

int main()
{
  Car acar;

  std::cout << "Car rpm: " << acar.amotor.mstates.rpm << std::endl;

  std::cout << "Saving states..." << std::endl;
  std::vector<std::unique_ptr<StateClass>> savedState = acar.saveState();

  std::cout << "Changing car rpm..." << std::endl;
  acar.amotor.mstates.rpm = 5000;
  std::cout << "Current car rpm: " << acar.amotor.mstates.rpm << std::endl;

  StateClass* tmpState = savedState.at(0).get();
  Motor::motorstates* tmpMotorstates = static_cast<Motor::motorstates*>(tmpState);
  std::cout << "Saved rpm: " << tmpMotorstates->rpm << std::endl;

  std::cout << "Loading state... " << std::endl;
  acar.loadState(savedState);
  std::cout << "Current car rpm: " << acar.amotor.mstates.rpm << std::endl;

}

补充评论(见评论):

  • 我读到了访问者类,但它们似乎比编写可读代码更复杂。
  • 我实际上只需要在运行时保存状态,而不是文件
  • 汽车只是一个例子。我的真实项目使用了许多具有数百个状态的组件类。还有一些州应该恢复,其他州则没有。

1 个答案:

答案 0 :(得分:0)

设计问题

这些丑陋的东西是由Component结构和State结构之间的相互依赖性引起的设计问题的症状。

换句话说,您已定义了多态State,但大多数时候您使用它期望特定的状态子类。

此外,汽车的配置实际上也是一种状态:如果您要更改其配置中的任何内容,您将无法再恢复任何内容(即使您刚刚添加了无关的备件)。

让您的代码更健壮

如果你保留这个设计,你应该在任何情况下使代码更健壮。例如,如果偶然发现一个不符合预期的状态指针,会发生什么?

motorstates* savedState = static_cast<motorstates*>(states);   

这将是UB!幸运的是,您的状态已经是多态类型。所以你可以使用动态强制转换:

motorstates* savedState = dynamic_cast<motorstates*>(states);  
if (savedState==nullptr) {  // this is true if the state was not of correct class
    //ouch !  At least you'd know
} 

(顺便说一句,根据经验,可以防止出现问题:如果你有虚拟成员函数,最好给你的类一个虚拟析构函数。)

替代设计1:纪念品

保存/恢复功能的一个好选择是使用memento design pattern。该想法是该对象从纪念品中保存或恢复其状态,其内容和结构未知来自“看护人”(即负责保存memnto并调用保管的代码)。

结果:CarMemento的内部结构仅为汽车所知。这会导致您无法恢复部分状态(例如仅限于引擎状态)。纪念品不一定是多态对象:保存状态/恢复状态无论如何都会为不同的组件使用特定的Memento类型。

您似乎将组件作为私有成员而不是动态项。如果确认这将是最安全的方法。

替代设计2:复合

另一种方法是为您的组件采用composite design pattern并将其与纪念品结合使用。

然后我使用序列化原理实现状态保存(即使它是内存对象),并且不仅保存状态而且保存完整的复合结构。然后,我会使用工厂反序列化已保存的状态。

但是,如果您只想保存对象的状态,可以将复合材料与纪念品结合使用:

  • 给memnto的内部结构一个复合结构,假设它总是复制保存的组件。
  • 或使用一些唯一标识来标识汽车中的组件,并将纪念品构造为地图容器,该容器返回标识符的状态。这种方法的优势在于,如果您在保存和恢复状态之间添加或删除您的关注组件,它会非常灵活。

但是,在这两种情况下,您都必须使用动态强制转换来确保已保存状态的类型与要还原的状态类型相匹配。