【问题标题】:Does using callbacks in C++ increase coupling? [closed]在 C++ 中使用回调会增加耦合吗? [关闭]
【发布时间】:2009-11-13 08:10:11
【问题描述】:

第一季度。为什么要用回调函数?

第二季度。回调是邪恶的吗?对那些人来说很有趣 谁知道呢,对别人来说是一场噩梦。

第三季度。回调的任何替代方案?

【问题讨论】:

    标签: c++ oop callback


    【解决方案1】:

    回调减少了耦合——被调用方被传递了一些指针,它不知道它背后是什么。回调是如此幸运的解决方案,以至于它们非常普遍。

    例如查看sqlite3_exec()。你给它一个查询和一个可选的回调。它执行查询并在检索到每一行时调用回调。现在,快速执行查询和低资源消耗是 SQLite 的业务,您只需按照自己的意愿处理检索到的结果。您可以将它们添加到容器中并稍后处理,或者您可以立即一个接一个地处理它们,或者您可以将它们传真到某个地方并期望对方将它们传真回来 - SQLite 不在乎,它是完全抽象的,可以做好自己的工作。

    【讨论】:

      【解决方案2】:

      无论是否“在 C++ 中使用回调增加耦合”,我都建议使用事件处理程序样式,尤其是处理类似事件的事情。例如,具体一个设计模式而不是回调概念如下:

      class MyClass
      {
      public:
          virtual bool OnClick(...) = 0;
          virtual bool OnKey(...) = 0;
          virtual bool OnTimer(...) = 0;
          virtual bool OnSorting(...) = 0
          ...
      };
      

      您可能仍然认为上述函数是回调,但是当您将它们视为已知的设计模式时,您不会感到困惑,因为您正在做 OO 并编写 C++。

      Effo UPD@2009nov13 - 典型案例:框架、事件系统或并发编程模型等。以下示例应该很有用。

      框架控制着整个流程,因为好莱坞原则规定“不要打电话给我们,我们会打电话给你”。 (这就是“回调”的确切含义)在每个普通函数或库中,调用者控制流程。

      著名的 C 框架是 Linux 内核,Linux 驱动程序编写者知道他/她将在其中实现“struct file_operations”“read()”表示 OnRead(),“write()”表示 OnWrite( ) 等。

      struct file_operations {
              struct module *owner;
              loff_t (*llseek) (struct file *, loff_t, int);
              ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
              ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
              ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
              ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
              ...
      };
      

      但最简单的框架示例应该是:

          The Framework           |     A developer to do
      ----------------------------+--------------------------------------
                                  |    class MyClass : public Actor
                                  |    {     
                                  |    public:
      pApplication->Init(...);    |        virtual bool OnInit(...) {}
      pApplication->Run(...);     |        virtual int OnRun(...) {}  
      pApplication->Quit(...);    |        virtual void OnQuit(...) {}
                                  |        ...
                                  |    };
      

      和pApplication->Init()会调用pActor->OnInit,pApplication->Run()会调用pActor->OnRun(),以此类推。大多数 Windows GUI 开发人员都有实现 OnMouseClick() 或 OnButtonPress() 等的经验。

      我同意该线程的其他答案,他们根据相应的观点给出了正确的解释,例如分层方法中的处理程序,通用回调或异步操作等。适合您的想法由您决定。

      【讨论】:

      • 您的解释引起了我的注意,但我无法正确理解。你能用伪代码解释更多吗
      • 您所指的设计模式是什么?我认为这是在 C++ 中工作的正常方式。您的回调处理程序派生自 MyClass(它不是​​类而是接口)并注册到使用该接口的类。它类似于观察者设计模式,但是您应该允许多个观察者,这在您的示例中并不明显,并且并不总是需要回调。
      • 你一直在谈论一种“设计模式”,但不说是哪一种?
      • @Pod 和@stefaanv:当我说“设计模式”时,我在回答中的意思是,人们可以摆脱“回调”的感觉,而只是认为你正在具体化一种特定的设计模式.许多设计模式使用回调机制。谢谢
      • @Effo:不,您对设计模式的使用仍然让我感到困惑。这很不幸,因为设计模式应该让事情变得更清晰。
      【解决方案3】:

      第三季度。回调的任何替代方案?

      我更喜欢回调的函子形式。例如这种事:

      class WidgetContainerOrProcessorOfSomeSort
      {
      public:
        struct IWidgetFunctor
        {
          virtual bool operator()( const Widget& thisWidget )=0;
        };
        ....
        bool ProcessWidgets( IWidgetFunctor& callback )
        {
           .....
           bool continueIteration = callback( widget[ idxWidget ] );
           if ( !continueIteration ) return false;
           .....
        }
      };
      
      struct ShowWidgets : public WidgetContainerOrProcessorOfSomeSort::IWidgetFunctor
      {
        virtual bool operator(){ const Widget& thisWidget }
        {
           thisWidget.print();
           return true;
        }
      };
      
      WidgetContainterOrProcessorOfSomeSort wc;
      wc.ProcessWidgets( ShowWidgets() );
      

      对于简单的示例来说似乎总是有点冗长,但在现实世界中,我发现它比试图准确地记住如何构造复杂的函数指针声明要容易得多:-)

      【讨论】:

      • 感谢您提供详细的示例。
      【解决方案4】:
      1. 回调减少耦合,因为它们允许您编写调用可更改函数的代码
      2. 回调并不邪恶,只是如果您使用原始函数指针,事情很快就会变得一团糟。
      3. 回调本身没有替代方法,但使用原始函数指针有替代方法。

      之前为 Boost.Function 发布了一篇指向 Boost 的帖子。如果您正在为回调寻找更通用的解决方案,例如附加到同一个回调的多个函数或类似的东西,请考虑使用Boost.Signals。这个名字来源于信号和槽,这是现在一些人提到回调的方式,尤其是对于 GUI。

      【讨论】:

        【解决方案5】:
        1. 回调用于调用异步操作,即在调用者按照自己的方式运行时在不同线程中运行的代码。您需要一些机制来知道异步操作何时完成。

        2. 为什么他们会是邪恶的?与任何其他编程资源一样,它们在明智地使用时很有用。事实上,例如,Windows API 和 .NET 框架广泛使用回调。

        3. 不了解 C++,但在 .NET 世界中,同步对象和事件是备选方案。

        【讨论】:

        • 对于#1,当然,回调对此很有用,但回调通常用于完全同步的功能......
        • 我没有听说过线程通信的回调,维基百科也没有提到它 (en.wikipedia.org/wiki/Callback_%28computer_science%29)。不过,回调处理程序可以将消息发送到其他线程。
        【解决方案6】:

        如果我们在 C++ 上下文中,请考虑使用(例如generalized callbacks)。

        背后的基本思想是回调(类方法)可以是任何名称,并且回调不需要派生自回调执行器已知的某个类。

        回调的唯一限制是输入参数和返回值。所以这将夫妇减少到零...... :)

        UDP:

        @EffoStaff Effo 的答案是一个示例,其中回调应属于特定类(派生)并具有固定名称。在泛化回调的上下文中,所有这些“限制”都不存在。

        【讨论】:

          【解决方案7】:

          第一季度。如果您使用分层方法,其中较高级别调用较低级别并通过回调从较低级别获取反馈,则需要回调。
          Q2。当采取一些预防措施时,它们并不比例如更糟。例外。在某些方面,它们是相似的。 Q3。更多的耦合:低爱知道高。

          备注:
          - 简单的方法(每个回调 1 个回调处理程序):通过接口注册 CallbackHandler 对象
          - 使用信号(QT、boost、...),并确保每个回调使用唯一的信号以增强可追溯性

          编辑:示例:

          用户调用ProtocolHandler发送消息,ProtocolHandler调用用户发送回复:相互依赖。

          分层:用户级别较高,ProtocolHandler 级别较低。在启动时,它为回复注册一个回调并调用 ProtocolHandler 发送消息。 ProtocolHandler 使用回调发送回复:只有用户依赖于 ProtocolHandler。

          【讨论】:

          • 关于Q1:如果使用特征X,显然需要特征X。
          • 它们减少了耦合。关键是下层不知道上层在做什么。它只有一个匿名指针。
          • 我认为 stefaanv 的意思是层之间的更多耦合是回调的替代方案。
          • @Devil Jin:马丁说得对,我被子问题弄糊涂了,所以我没有直接回答标题问题。对我来说,在考虑回调时分层考虑更清楚,以避免到处使用回调。
          【解决方案8】:

          在使用 Win32 风格回调的罕见情况下,请注意“模糊的可重入”问题(如果回调只是意味着将函数作为 arg 传递给无法想象并发问题的另一个函数,则以下内容无关紧要)。 Joe Duffy 的“Windows 上的并发编程”的出色索引列出了“可重入”下的 5 个主题,这些主题通常是“在同一线程上围绕 Robin-Hood 的谷仓递归”概念的专业化——换句话说,来自最受好评的-回答“被调用方传递了一些指针,它不知道它背后是什么。”,“不知道”有时可以引导-Robin-Hood's-barn。
          我刚才所说的没有什么是特定于回调的,但是如果被调用方偶然发现了 Duffy 的场景之一,那么“晦涩的重入”可能会发生。换句话说,隐含在“回调”概念中的是,您似乎将在同一个线程中被回调,这是如何发生的,它是如何同步的。
          如果您在 Duffy 的书名上使用 Google 搜索并将 Callback 这个词添加到您的搜索中,那么您将获得超过 10 页的点击量。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-12-04
            • 2016-06-25
            • 1970-01-01
            • 1970-01-01
            • 2021-08-23
            • 1970-01-01
            相关资源
            最近更新 更多