带有回调函数的 C++ 模板类

Posted

技术标签:

【中文标题】带有回调函数的 C++ 模板类【英文标题】:C++ template class with callback function 【发布时间】:2019-10-15 11:35:31 【问题描述】:

尽管我阅读了很多关于模板的内容,但这是我第一次尝试实际使用它们。因此我的问题可能只是缺少对模板的理解。

我有一个应该独立并定期生成一些数据的类。当有新数据时,它通过回调函数调用父级并发送数据。该类应该是可移植的,因此是模板化的父类。

但是,如果我在我的数据类上使用模板,我必须将模板拖到每个函数中,而仅仅能够调用一个函数就觉得不必要的复杂。所以我在全局命名空间中创建了一个小的回调类,我想将其用作接口。

namespace wrapper

    //Callback class
    template<typename ParentClass>
    class Data_Callback
    
    public:
        ParentClass* m_parent = nullptr;
        Data_Callback<ParentClass>(ParentClass* parent)
        
            m_parent = parent;
        ;

        void DoCallback(DataClass* data)
        
            if (m_parent)
                m_parent->callback_func(*data);
        
    ;

    //global object I want to use for callback
    template<typename ParentClass>
    std::unique_ptr<Data_Callback<ParentClass>> m_parentCallback;

    template<typename ParentClass>
    PatientenErkennung GetNewDataClass(ParentClass* parent)
    
        m_parentCallback = std::make_unique<Data_Callback<ParentClass>>(parent);

        std::shared_ptr<DataClass> obj =  std::make_shared<DataClass>();
        return obj;
    ;

然后我可以像这样从我的父类调用我的全局“GetNewDataClass”函数:

m_data = wrapper::GetNewDataClass(this);

但是当我想从 DataClass 调用回调时,模板会妨碍:

wrapper::m_parentCallback<I_DONT_HAVE_THIS>->DoCallback(data);

在不知道父类的情况下,我不能调用那个全局对象,可以吗?因此回调只能从 Data_Callback 类初始化,但该类不包含数据。

你如何解决这个问题?可能是继承?还是设计本身不能像这样使用? 正如我之前所说,我想避免让我的较大的 DataClass 在 DataClass 周围拖动模板,尽管这会起作用...

【问题讨论】:

您目前拥有的是针对每种父类类型的不同全局对象。这是你在概念上想要的,还是你想要一个所有东西都应该使用的全局回调变量? 如果你使用“继承”——好吧,在这种情况下,更好的术语是“多态性”,那么你就不再需要包装器了。您将为所有类型的父类设计一个公共接口,然后每个单独的类型都将从该公共基础继承。但是,如果这适合您的需求,则取决于您打算如何使用这些父母... @MaxLanghof 在我的情况下它实际上是相同的,因为只有一个 DataClass 实例并且它与回调一起被初始化。不过,您是对的:它原本应该是一个全局对象。 @Aconcagua 我只是将包装器用作某种解决方法。我不依赖它。只有一个父类匹配一个 DataClass 对象,我只需要那个父对象用于回调函数。指向成员回调函数的指针也可以,但我认为这种方法在 C++ 中既不好也不容易。您是否有一个示例如何在我的 DataClass 中没有模板的情况下继承父指针? struct Base virtual ~Base() ; virtual void f() = 0; ; struct Derived : Base void f() override ; Base* b = new Derived(); b-&gt;f(); – 由于 f 是虚拟的,因此在给定示例中,将调用可用的最派生类的变体 Derived::f,即使指针的类型为 Base 【参考方案1】:

您将需要类型擦除,您可以手动滚动。但是该标准已经为任何类型的函数(包括回调)提供了一个通用接口:std::function(它在内部执行您需要的类型擦除)。

namespace wrapper

    //global object I want to use for callback
    std::function<void(DataClass&)> m_parentCallback;

    template<typename ParentClass>
    auto GetNewDataClass(ParentClass* parent)
    
        m_parentCallback = [parent](DataClass& dc) parent->callback_func(dc); ;

        std::shared_ptr<DataClass> obj =  std::make_shared<DataClass>();
        return obj;
    ;

m_parentCallback 被声明为std::function,它接受DataClass 引用并且不返回任何内容。我们在 GetNewDataClass 中使用 lambda 对其进行初始化,该 lambda 捕获 parent 指针的值,并在该指针上调用 callback_func,并将 DataClass 实例传递给它。

(您实际上可以在这里跳过 lambda,直接写

 m_parentCallback = parent->callback_func;

但是 lambda 更灵活,因此“规范地”用于此目的。另外,如果parent 没有callback_func,您可能会收到更好的错误消息。)

要使用它,只需像函数一样调用它:

wrapper::m_parentCallback(data);

https://godbolt.org/z/nUG9Bx

您可能知道,但我强烈反对以如此紧密联系的方式使用全局变量。目前(并且可能在您项目的“生命周期”中)它可以正常工作,但这确实无法扩展。 wrapper 可能本身就是一个类。

【讨论】:

谢谢。除了由于多个定义导致的几个链接器错误外,这工作正常。对于您添加的评论:我认为拥有回调函数应该很常见,至少自从工作线程出现以来。那么必须已经有一种通用/更好的方法了吗?我想解决我的问题,但我也对如何解决问题感兴趣。 问题不在于回调函数本身的概念,而是假设只有一个“客户”在使用回调函数。如果您有两个不同的父类实例,它们都应该从(不同的)数据生成器接收(不同的)回调,那么您的全局变量方法将分崩离析。现在不要太担心它,但要小心使用全局变量可能会伤害自己。

以上是关于带有回调函数的 C++ 模板类的主要内容,如果未能解决你的问题,请参考以下文章

带有类方法的 Cython 回调

如何为 Python Swigged C++ 对象创建和分配回调函数

使用 C++ 类成员函数作为 C 回调函数

C++中类成员函数作为回调函数

将函数模板定义为类模板的回调

C++ 选择题总结(回调函数 || 类方法(实例方法)|| )