带有回调函数的 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->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++ 模板类的主要内容,如果未能解决你的问题,请参考以下文章