【问题标题】:C++ nonmember accessing member functionsC++ 非成员访问成员函数
【发布时间】:2014-07-29 13:02:28
【问题描述】:

我正在与另一个团队合作开展一个项目。另一个团队正在构建一个 GUI,与大多数 GUI 框架一样,它是非常受继承驱动的。另一方面,这一边的代码('底端',我猜有人可以说)本质上是 C (尽管我相信通过 MSVC2010 工具链在技术上都是 C++,没有“视为 C”标志。

两个模块(UI 和 this)必须单独编译,然后链接在一起。

问题: 需要在底端调用 GUI 端的重绘函数,并为其提供一些数据。现在事情变得糟糕了。您如何调用 INTO 一组成员函数,尤其是具有复杂依赖关系的成员函数?如果我尝试包含窗口标题,则有一英里长的 GUI 内容有一个继承列表,底端显然不是针对复杂的 GUI 库构建的……我无法转发声明我的出路,因为我需要在窗口上调用函数?

现在显然这是一个重大的沟通设计缺陷,尽管我们现在处于不利的境地,重大重组并不是真正的选择。

问题:

  1. 应该如何组织它以使底部与顶部接触以进行重绘,从类似 C 的代码球到 C++ 节点球。

  2. 我现在可以做些什么来规避这个问题?

我能想到的唯一好方法是使用某种通信类......但我不认为这不会遇到同样的问题,因为它需要同时针对 GUI 和底线?

【问题讨论】:

  • 底层设置脏标志或者调用回调什么的怎么样,然后让高层代码做需要访问高层的东西
  • 在windoze上,触发重绘的常用方法是发布消息。 “低”代码可以发布一条消息,UI 在消息队列中拾取该消息。除此之外,很难从您对问题的描述中看出。
  • 正如 Alf 所说,回调听起来不错 - 例如您可以让 GUI 代码设置一个函数指针(如果您喜欢冒险,甚至可以设置 std::function),当“C”代码到达它知道可能需要重绘的点时,它通过函数指针调用(如果不是nullptr)。 GUI 端总是可以提供一个函数指针,指向一些存储上下文的中间函数(你的“给它一些数据”)然后调用成员函数。
  • 至于使用函数指针——我不会遇到同样的问题吗?非成员函数指针与成员函数指针不同。在声明成员函数指针类型时,必须指定类。 “较低级别”仍然需要了解 GUI 堆栈类,不是吗?不确定成员函数 ptr 的类是否可以是外部的……如果不是,那将是同样的问题。 @user3344003:它使用的是 QT,而不是 Win32 GUI 堆栈。要发布任何消息,它需要了解 GUI 框架 - 即使在 Win32 中也是如此。

标签: c++ oop


【解决方案1】:

如果您只需要调用一个函数,甚至是一小部分函数,​​那么回调可能是您最好的选择。如果您正在处理成员函数,您仍然可以使用指向成员函数的指针和指向相关对象的指针来调用它。请参阅this answer for details on doing that. 但是,这可能意味着您需要包含整个一英里长的 GUI 代码依赖项列表。

编辑:经过一番思考,您可以对一些函数进行回调,而无需包含 GUI 代码的依赖项。例如:

在某处的 GUI 代码中...

int DoFooInBar(int arg1, const char *arg2){
    return MyForm.ChildContainer.ChildBox.ChildButton.Bar.DoFoo( arg1, arg2 );
}

现在在 GUICallbacks.hpp...

int DoFooInBar(int arg1, const char *arg2);

然后,您可以在 C 代码中的任何位置包含 GUICallbacks.hpp 并调用 DoFooInBar()。此方法的唯一问题是您需要为要使用的每个回调创建一个新函数。

批量完成此类任务的更通用方法是通过传递消息。正如您所提到的,一种非常跨平台的方法涉及通信对象。如果您提供一种通过命名机制获取指向共享通信对象的指针的机制,则不一定会遇到任何构建问题。一个小例子是:

class CommObj{
public:
    struct Message{
        uint32_t type;
        uint32_t flags;
        std::string title;
        std::string contents;
        ... //maybe a union here or something instead
    };
private:
    static map<std::string, CommObj*> InternalObjects;
    std::deque<Message> Messages;
    std::string MyName;
public:
    CommObj(const char *name); //Registers the object in the map
    ~CommObj(); //Unregisters the object in the map
    void PushMessage( uint32_t type, uint32_t flags, const char *title, const char *contents, ...);
    Message GetMessage();
    bool HasMessages();
    static CommObj *GetObjByName(const char *name);
    static bool ObjWithNameExists();
};

显然,您可以制作一个更像 C 的版本,但是为了清楚起见,这是在 C++ 中的。实现细节供读者练习。

使用此代码,您可以简单地针对该对象构建后端和前端,并且您可以在代码的两侧运行检查以查看名称为 "Backend-&gt;GUI" 的 CommObj 是否已生成。如果没有,那就做吧。然后,您将能够通过使用GetObjByName("Backend-&gt;GUI"); 获取指向它的指针来开始与该对象通信,然后您将不断地轮询该对象以查看是否有任何新消息。您也可以让 GUI 使用另一个对象来向后端发布消息,可能命名为 "GUI-&gt;Backend",或者您可以在对象本身中构建双向性。

另一种方法是使用套接字通信/共享文件描述符。然后,您可以读取数据并将数据写入套接字以供另一方接收。对于基本的信号,这可能是完成您需要的简单方法,特别是如果您真的不需要任何复杂的东西。对套接字描述符的简单send() 调用将是您向代码的另一端发出信号所需的全部内容。

请注意,如果大量使用套接字,可能会导致速度下降。这取决于底层实现,但 localhost 上的套接字通常比原始函数调用慢。不过,您可能不需要紧密循环中的互锁信号,因此您应该可以使用任何一种方法。当我说慢一点时,我的意思是它可能是 50 微秒对 5 微秒。在大多数情况下,这并不是真正需要过多担心的事情,而是需要注意的事情。另一方面,如果 GUI 代码与后端代码在不同的线程中运行,您可能希望在发布/读取消息之前对通信对象进行互斥,而共享文件描述符不需要这样做。互斥锁/信号量需要自己处理。

使用像我给出的大纲那样的通信对象将允许对类型进行一些自动编组,您可能会对此感兴趣。当然,您也可以编写一个对象来使用套接字进行编组,但是在这一点你不妨使用共享对象。

我希望您的项目能够顺利进行。

【讨论】:

  • 是的,我宁愿远离套接字。它实际上是一个资源密集型应用程序,因此开销可能很重要。通信对象很好,但我不喜欢它必须不断轮询,我觉得这样的方式很糟糕。我不介意做那个回调,但是“MyForm”会有点棘手,因为 DoFooInBar 是一个非成员函数。我也许可以将“MyForm”保存在静态上下文中并从那里获取...不是我的首选方法,但它可能是可管理的...即使它涉及回调我希望该操作是直接函数调用
  • 对,对于回调机制,您必须做一些体操才能获得根表单元素。您可以考虑将 GUI 代码中的函数绑定到 std::function 中,然后您可以将其传递回后端代码。
猜你喜欢
  • 2013-12-26
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-30
  • 2013-05-03
相关资源
最近更新 更多