1

我正在设计一个 GUI,但我遇到了问题,因为我无法避免 dynamic_casting。

我的课程:

  class Widget; //base class for all widgets

  class Container //contains widgets
  {
     std::map<std::string, Widget*> m_widgets;
     public:
     template <class T> T* get(const std::string &name)
     {
         return dynamic_cast<T*>(m_widgets.at(name)); //I need casting here
     }
   }

如何避免动态投射?我不能为每种小部件类型都拥有容器,因为我的 GUI 必须与用户定义的小部件一起使用。此外,我必须为每个小部件都有一个容器,这样用户就不必自己存储小部件。

为什么我需要铸造?

 class TextBox : public Widget
 { 
      public:
      std::string getText(); //I can't have it in Widget class, because it's object-specific
      //also, my gui must work with user-defined widgets so I can't provide 
      //empty virtual functions for everything in Widget
 }
4

4 回答 4

3

我认为您不需要包含所有小部件的容器。这些类可以保存指向它们需要使用的小部件的具体实例的指针。您可以将指针作为参数传递给构造函数;另一种选择是使用依赖注入(例如wallaroo可用于解决您的问题)。

于 2013-06-29T10:25:53.673 回答
2

要扩展我的评论……</p>

根据您的代码,我猜您打算让我为添加到 a 的每个子小部件指定一个字符串名称Container,然后再按名称访问子小部件。所以你会期望我写这样的东西:

class LoginController {

    Container *container;
    static const char *kUsernameKey = "username";
    static const char *kPasswordKey = "password";

public:

    LoginController() :
        container(new Container())
    {
        container->addChild(kUsernameKey, new TextBox());
        container->addChild(kPasswordKey, new TextBox());
        container->addChild("button", new Button("Log In"));
        container->get<Button>("button")->setAction([](){
            this->login();
        })
    }

    void login() {
        string username = container->get<TextBox>(kUsernameKey)->getText();
        string password = container->get<TextBox>(kPasswordKey)->getText();
        sendLoginRequest(username, password);
    }

};

以这种方式设计Container会在运行时进行查找和类型检查,但这些查找和类型检查可以在编译时完成。

相反,设计 API 以便我在自己的变量中保留对子项的特定类型的引用。查找孩子只需使用变量,无需强制转换。查找和类型检查在编译时完成。代码如下所示:

class LoginController {

    Container *container;
    TextBox *usernameBox;
    TextBox *passwordBox;

public:

    LoginController() :
        container(new Container()),
        usernameBox(new TextBox()),
        passwordBox(new TextBox())
    {
        container->addChild(username);
        container->addChild(password);

        Button *button = new Button("Log In");
        container->addChild(button);
        button->setAction([](){
            this->login();
        })
    }

    void login() {
        string username = usernameBox->getText();
        string password = passwordBox->getText();
        sendLoginRequest(username, password);
    }

};
于 2013-06-29T18:25:18.057 回答
1

我到处都读到 RTTI 不好,应该避免

我怀疑你到处都读过,因为那是纯粹的胡说八道。

RTTI 是一个非常好的功能。尽管在可以使用更好的工具的情况下绝对应该避免使用它。一个好的层次结构被设计为可以通过虚函数访问基类接口。在这些情况下,您不需要任何强制转换,只需调用虚函数,它就会做正确的事情。

即使您的 GetText 也可能是默认实现返回空字符串的公平候选者。也许与报告实际文本存在的功能查询工具一起使用。因此,大多数客户可以直接调用 end 对空字符串感到满意,而其他客户可能会进行检查。

那些对一些罕见的接口或只是一个具体类感兴趣的人可以永久调用 dynamic_cast。收集最好保持简单并仅限于收集。也许为真正常见的 Widget 系列添加一些特殊形式。

于 2013-06-29T12:50:30.220 回答
0

这里的问题是你的设计是倒退的。你想渲染一个 Widget,但 Widget 没有具体的可渲染特性。您必须要求 Widget 呈现自己,它可以在其中呈现文本/图像/任何它想要的东西。

于 2013-06-29T18:30:53.643 回答