如何在c ++中避免许多静态强制转换和nullptr检查?

时间:2017-12-13 18:58:56

标签: c++ algorithm design-patterns

因此,大型遗留代码库的结构看起来像这样。

我们有一个具有此公共界面的基本应用程序:

// Base app
class BaseApp {
public:
   ...
   Widget* getUiWidget(string _widgetName);
   ...
}

但是,我们的小部件就像这样实现

// Widget structure
class Widget {
...
}

class Button : public Widget {
...
}

class Label : public Widget {
...
}

在我们的视图代码中,问题无处不在,我们有一堆看起来像这样的调用:

auto button = static_cast<Button*>(getUiWidget(buttonName));
if (button != nullptr) {
  // something happens
  button->doSomething1();
}

auto label = static_cast<Label*>(getUiWidget(buttonName));
if (label != nullptr) {
  // something happens
  label->doSomething2();
}

我个人不喜欢这样,因为有数千个这样的电话,我觉得可以做得更好。

我的直觉告诉我,我可以做这样的事情

// Base app
class BaseApp {
public:
   ...
   Widget* getUiWidget(string _widgetName);
   Label* getLabel(string _labelName) {
     if (//labelName is in widget layout) 
        return new NullLabel;
     else 
        return new Label(_labelName)
   }
   Button* getButton(string _buttonName) {
     if (//buttonName is in widget layout) 
        return new NullButton;
     else 
        return new Button(_buttonName)
   }
   ...
}

空对象

的位置
class NullButton : public Button {
public:
  // override all methods of button and do nothing with it
}

class NullLabel : public Label {
public:
  // override all methods of Label and do nothing with it
}

所以代码:

auto button = static_cast<Button*>(getUiWidget(buttonName));
if (button != nullptr) {
  // something happens
  button->doSomething();
}

// Would turn in to 

auto button = getButton(buttonName);
button->doSomething1();

我对这种方法的问题是我们有n个小部件子项,比如切换,滑块等。所以,为了实现这个,我需要n个getSomeWidgetChild而且我还需要写n NullSomeWidgetChild

有更好的解决方案吗?

3 个答案:

答案 0 :(得分:2)

可以使用访问者模式来避免动态强制转换。 (问题中标明的静态演员是危险的!)

  1. 在窗口小部件层次结构中定义新的接受方法。
  2. 创建一个新的访问者类,其中包含您在重载的访问函数中要执行的所有特定对象“执行某些操作”。
  3. 添加访客类:

    class Visitor {
    public visit (Button* button) { /* do button specific work */}
    public visit (Label* label) { /* do label specific work */}
    }
    

    添加接受方法:

    // Widget structure
    class Widget {
    virtual public accept (Visitor * v);
    ...
    }
    
    class Button : public Widget {
    public accept (Visitor * v) override {v->visit(this);}
    ...
    }
    
    class Label : public Widget {
    public accept (Visitor * v) override {v->visit(this);}
    ...
    }
    

    现在您的代码将更改为:

    Visitor* v = new Visitor;
    auto button = getUiWidget(buttonName);
    button.accept(v);
    

答案 1 :(得分:0)

通常,您使用多态和动态绑定来克服甚至转换返回对象的需要:

class Widget {
  virtual void someTypeSpeficifBehaviour() = 0;
}
class Button : public Widget {
  virtual void someTypeSpeficifBehaviour() { // button specific };
}
class Label : public Widget {
  virtual void someTypeSpeficifBehaviour() { // label specific };
}
int main() {
  Widget *w = baseApp->getUIWidget("test");
  w->someTypeSpeficiBehaviour();
}

你无法以这种方式处理它,你仍然可以在对象中包含一个类型信息:

class Widget {
  virtual bool isButton() = 0;
}
class Button : public Widget {
  virtual bool isButton() { return true; }
}
class Label : public Widget {
  virtual bool isButton() { return false; }
}
int main() {
  Widget *w = baseApp->getUIWidget("test"); 
  if (w->isButton()) { // button specific
  }
  else { // else...
  }
}

如果这一切都不起作用,您可以考虑dynamic_cast<> - 检查(而不是static_cast)。但通常dynamic_cast<>应该让你重新思考设计。

答案 2 :(得分:0)

您的示例代码不安全。

由于static_cast在转换之前执行了没有运行时类型信息(RTTI)检查,因此那些nullptr检查将不会捕获转换失败。

使用RTTI可以预期那些是dynamic_cast。

在处理多态时不替换RTTI替换测试转换需要某种形式的窗口小部件类型的枚举以及类中类型枚举的存储作为静态const。

如果你不介意RTTI,那么使用typeid并打开结果。

由于Button或Label的类型以及关联的hash_code在编译时是已知的常量,因此开关应该可以正常工作,但如果我错了,你总是可以使用if语句。

e.g:

auto widget = getUiWidget(buttonName);
switch(typeid(*widget).hash_code())
{
    case typeid(Button).hash_code():
        {cast to Button, possibly do something, and break}
    case typeid(Label).hash_code():
        {cast to Label, possibly do something, and break}
}