统一处理具有协变类型的函数指针(如何使用派生类型调用回调?
Handling function pointer with covariant types uniformly (how to call callbacks with derived types?)
假设我有一个Task
类:
class Task;
using FinishedCallback = void (Task &);
class Task {
public:
// this function is implemented by derived classes
// there are other virtual functions too
virtual bool isFinished() = 0;
private:
std::vector<FinishedCallback> m_callbacks;
protected:
// these functions are called by derived classes
void addFinishedCallback(FinishedCallback callback) {
m_callbacks.push_back(callback);
}
void callFinishedCallbacks();
};
此类是基类。我想尽可能多地实现它。它有一个m_callbacks
向量,它存储任务完成时需要调用的回调(callFinishedCallbacks()
这样做(。
现在,从这个类派生:
class MyTask;
using MyFinishedCallback = void (MyTask &);
class MyTask: public Task {
public:
void addFinishedCallback(MyFinishedCallback callback) {
Task::addFinishedCallback(reinterpret_cast<FinishedCallback &>(callback));
}
// when MyTask finishes, it calls callFinishedCallbacks
};
在这里,完成的回调具有MyTask &
参数,因此我必须reinterpret_cast它。所以我的程序有 UB。(MyTask &
类型的原因是我不必在回调中MyTask &
投Task &
- 这是为了方便起见(。
但是,即使我的程序有 UB,它也可以工作(如果MyTask
有多个继承,我可能遇到的唯一真正问题(。是否可以修改此程序以删除 UB,同时保留其主要属性:
- (编译(代码很小
- 需要少量编码工作,如果一个来自任务
- 回调不需要包装器
一个想法是对你编写回调的方式进行轻微的改变。
- 使用
Task *
而不是Task &
作为回调参数类型。 -
在回调开始时,使用
dynamic_cast
将Task *
转换为指向派生类型 (MyType *
( 的指针。void MyCallback(Task *task) { auto *mytask = dynamic_cast<MyTask *>(task); assert(mytask != nullptr); // use mytask from here on }
另一个想法是使用奇怪的重复模板模式(CRTP(。
class BasicTask {
public:
virtual ~BasicTask() = default;
virtual bool isFinished() = 0;
};
template <typename Self>
class Task : public BasicTask {
private:
typedef void FinishedCallback(Self &task);
std::vector<FinishedCallback> m_callbacks;
protected:
void addFinishedCallback(FinishedCallback callback) {
m_callbacks.push_back(callback);
}
void callFinishedCallbacks();
};
然后,您将从任务中派生特定的任务类型,如下所示:
class MyTask : public Task<MyTask> { ... };
这减少了您必须编写的代码量(因为只有一个源代码实现addFinishedCallback
(,但它冒着生成更多目标代码的风险。 优化器和链接器可能能够控制这种可能性。
相关文章:
- 类型总是使用其大小存储在内存中吗
- 为什么在我的函数类型后使用引用运算符 (&) 允许我修改它返回的值?
- 使用 make 编译 MPI,几个命名空间错误,例如"错误:未知类型名称'使用'?
- 在模板化成员函数的返回类型中使用 std::enable_if 时的编译器差异
- Google Sparsehash 在类型上使用 realloc(),这很难复制
- 将 lower_bound/upper_bound 与 2 种不同的类型一起使用
- 根据动态选择类型C++模板使用情况
- 不允许将SDL_Cursor与unique_ptr:error不完整类型一起使用
- 为什么引用类型在使用临时对象访问时是左值
- 在枚举类型上使用std::max是不是一种糟糕的做法
- 将提升属性映射与捆绑类型一起使用
- 使用 std::future 的不完整类型无效使用
- 为什么在函数参数类型中使用模板参数包作为其模板参数列表无法显式指定
- 如何将模运算符与其他数据类型一起使用
- 澄清了双精度数据类型的使用
- 类型检测:使用variadic参数正确实现计算平均值的函数
- 如何使用%类型指令使用std ::变体类型
- C++ 在知道变量类型之前使用自动定义的变量
- 在任何类类型上使用模板方法中的 new
- 如何将STD :: Iterator与基本类型Uint8_t**使用