提问人:Christopher Dickey 提问时间:9/28/2023 最后编辑:YksisarvinenChristopher Dickey 更新时间:9/28/2023 访问量:94
如何让编译器代替我复制此代码?
How do I get the compiler to duplicate this code instead of me?
问:
我正在开发一个由MIDI消息控制的合成器,有时同时来自多个MIDI源。它基于 Teensy 4.1。
我编码它使用的第一个源是 USB 设备 MIDI——很简单:在 setup() 中设置 8 个回调并在 loop() 中调用 usbMIDI.read()。
我编码它使用的第二个来源是 USB Host MIDI。但是,我最终得到了重复的代码 - 所有回调设置代码和 loop() 中的 read() 调用。
然后,我又添加了几个 USB 主机 MIDI 源对象,创建了一个指向主机对象的指针数组,现在我在 setup() 和 loop() 中迭代该数组......但仍有 USB 设备接口的该代码的副本。
供参考:
void setup()
{
// USB Device
usbMIDI.setHandleNoteOff(myNoteOff);
usbMIDI.setHandleNoteOn(myNoteOn);
usbMIDI.setHandlePitchChange(myPitchChange);
...
// USB Host
for(uint8_t i = 0; i < CNT_MIDI; i++)
{
midiDevices[i]->setHandleNoteOff(myNoteOff);
midiDevices[i]->setHandleNoteOn(myNoteOn);
midiDevices[i]->setHandlePitchChange(myPitchChange);
...
}
}
...
void loop()
{
usbMIDI.read();
for(uint8_t i = 0; i < CNT_MIDI; i++)
midiDevices[i]->read();
...
}
usb_midi_class对于这些调用(read()、setHandle...(函数指针)),但由于它们没有通用的基类,因此我不能将 USB 设备 MIDI 对象添加到 USB 主机指针数组中。
现在,我想通过 Teensy 的一个硬件串行端口添加一个 5 针 DIN MIDI 端口,并且将有三个相同调用的副本......我想知道是否有更好的方法。
我的目标是在所有这三种类型上调用同名的成员函数,而无需将这些调用写出三次。如果编译器这样做,我不会打扰。我也不打算在setup()之后添加或删除对象;我知道编译时的来源是什么。
这感觉像是模板的工作,但对我来说,如果不以另一种方式复制所有代码,我将如何完成这项工作并不明显。
例如,我想我可以编写一个通用的基类、9 个虚拟函数,并创建一个派生的模板类来继承它,然后定义这 9 个虚拟函数,例如:
class Wrapper
{
public:
virtual bool read(uint8_t channel) = 0;
virtual ~Wrapper() = 0;
...
};
template <class T>
class TemplateWrapper : public Wrapper
{
private:
T* wrapped;
public:
TemplateWrapper(T* w) : wrapped(w) {}
bool read(uint8_t channel) { return wrapped->read(channel); }
...
};
但它仍然让我重写了几次代码,只是在不同的地方。另外,我必须用函数指针写出 8 个签名。
有没有更好的方法?
答:
使用模板是个好主意,但您不需要为每个对象实际创建基类。你可以创建一个函数 witch 接受对某个模板化参数的引用,并在其上调用你的函数。
template <typename T>
void setupDevice(T& device) {
device.setHandleNoteOff(myNoteOff);
device.setHandleNoteOn(myNoteOn);
device.setHandlePitchChange(myPitchChange);
}
您还可以重载该函数以接受指针而不是引用,因为您在显示的代码中同时使用两者。
template <typename T>
void setupDevice(T* device) {
device->setHandleNoteOff(myNoteOff);
device->setHandleNoteOn(myNoteOn);
device->setHandlePitchChange(myPitchChange);
}
如果您的编译器支持 C++17 及更高版本,您也可以使用折叠表达式一次性将所有设备传递给您的函数。
template <typename... Args>
void setupDevice(Args... args) {
(args.setHandleNoteOff(myNoteOff), ...);
(args.setHandleNoteOn(myNoteOn), ...);
(args.setHandlePitchChange(myPitchChange), ...);
}
正如@Yksisarvinen所指出的,这会将相同的参数传递给所有方法,因此您当然可以更改我的函数以接受这些附加参数,如下所示(我只是假设它们的类型,但根据您的需要更改它们):.setHandle...()
setupDevice()
int
template <typename T>
void setupDevice(T& device, int myNoteOff, int myNoteOn, int myPitchChange) {
device.setHandleNoteOff(myNoteOff);
device.setHandleNoteOn(myNoteOn);
device.setHandlePitchChange(myPitchChange);
}
评论
上一个:Java DRY 无泄漏函数
下一个:如何为此 VBA 宏创建循环?
评论
std::variant
的花哨解决方案。