C++ 中的双重调度
Double Dispatch in C++
我需要以下类型的调度功能。在我的应用程序中,我真正拥有的是指向状态基 class (foo) 的指针以及指向测量基 class (bar) 的指针。基于传递给 dispatch2 函数的派生实例,我需要在给定状态的情况下生成估计测量值。例如,派生状态 class 可以是位置,派生测量 class 可以是 ToF(飞行时间)。然后,处理程序将从 f(例如发射器位置)获取状态信息以及从 b(例如传感器位置)获取收集器信息,并根据这些信息计算预期 ToF。然后将其返回并可以与实际测量值 (b) 进行比较。
string dispatch2(foo* f, bar* b) {
if ( dynamic_cast<Foo>(f) ) return foo1(f,b);
else if ( dynamic_cast<FOo>(f) ) return foo2(f,b);
else if ( dynamic_cast<FOO>(f) ) return foo3(f,b);
throw std::runtime_error("dispatch for f not defined");
}
string foo1(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo1bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo1bar2handler(f,b);
throw std::runtime_error("foo1: dispatch for b not defined");
}
string foo2(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo2bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo2bar2handler(f,b);
throw std::runtime_error("foo2: dispatch for b not defined");
}
string foo3(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo3bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo3bar2handler(f,b);
throw std::runtime_error("foo3: dispatch for b not defined");
}
string foo1bar1handler(foo* f, bar* b) {return "FooBar";}
string foo2bar2handler(foo* f, bar* b) {return "FooBAR";}
string foo3bar1handler(foo* f, bar* b) {return "FOoBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOoBAR";}
string foo2bar1handler(foo* f, bar* b) {return "FOOBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOOBAR";}
显然,没有办法避免为我想明确处理的每个组合定义结束方法的需要。但是,我正在寻找其他方法来实现这一点。理想情况下,某些模式允许用户显式注册每个处理程序,并且任何未处理的组合都可能引发运行时异常。任何建议,将不胜感激。谢谢
一种方法(当然不是唯一的方法)是调用 foo 上的虚函数,将其传递给 bar。 foo 的每个派生类型都以相同的方式实现此调度函数,它将自身传递给 bar 中的虚拟处理程序函数。
当您需要添加更多内容时,您可以扩展接口以接受新类型。所有 foo 函数都具有相同的实现,但它们有所不同,因此 "this" 是对象的正确动态类型。
另外,Andrei Alexandrescu 在他的(现在不那么现代的)书 Modern C++ Design 中用设计替代方案对此进行了很好的调查,该书仍然涵盖了这个想法,但是为 c++98 编写,但绝对仍然值得一读(尽管它说不能做的许多事情现在是 C++ 的一部分,部分原因是那本书。)
现场观看:https://godbolt.org/z/oRyVJx
此示例有 3 个 Foo 类 和 2 个 Bar 类。
#include <iostream>
class BarBase;
class FooBase {
public:
virtual ~FooBase() = default;
virtual void dispatch(BarBase*) = 0;
};
class Foo1;
class Foo2;
class Foo3;
class BarBase {
public:
virtual ~BarBase() = default;
virtual void accept(Foo1*) = 0;
virtual void accept(Foo2*) = 0;
virtual void accept(Foo3*) = 0;
};
class Bar1 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Bar2 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Foo1 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo2 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo3 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
void Bar1::accept(Foo1 * f) { std::cout << "Bar1 accepting Foo1\n"; }
void Bar1::accept(Foo2 * f) { std::cout << "Bar1 accepting Foo2\n"; }
void Bar1::accept(Foo3 * f) { std::cout << "Bar1 accepting Foo3\n"; }
void Bar2::accept(Foo1 * f) { std::cout << "Bar2 accepting Foo1\n"; }
void Bar2::accept(Foo2 * f) { std::cout << "Bar2 accepting Foo2\n"; }
void Bar2::accept(Foo3 * f) { std::cout << "Bar2 accepting Foo3\n"; }
//
// Doesn't know which types of foo and bar it has, but it doesn't matter...
//
void call(FooBase& foo, BarBase& bar) {
foo.dispatch(&bar);
}
int main() {
Foo1 f1;
Foo2 f2;
Foo3 f3;
Bar1 b1;
Bar2 b2;
call(f1, b1);
call(f2, b1);
call(f3, b1);
call(f1, b2);
call(f2, b2);
call(f3, b2);
}
输出:
Bar1 accepting Foo1
Bar1 accepting Foo2
Bar1 accepting Foo3
Bar2 accepting Foo1
Bar2 accepting Foo2
Bar2 accepting Foo3
我需要以下类型的调度功能。在我的应用程序中,我真正拥有的是指向状态基 class (foo) 的指针以及指向测量基 class (bar) 的指针。基于传递给 dispatch2 函数的派生实例,我需要在给定状态的情况下生成估计测量值。例如,派生状态 class 可以是位置,派生测量 class 可以是 ToF(飞行时间)。然后,处理程序将从 f(例如发射器位置)获取状态信息以及从 b(例如传感器位置)获取收集器信息,并根据这些信息计算预期 ToF。然后将其返回并可以与实际测量值 (b) 进行比较。
string dispatch2(foo* f, bar* b) {
if ( dynamic_cast<Foo>(f) ) return foo1(f,b);
else if ( dynamic_cast<FOo>(f) ) return foo2(f,b);
else if ( dynamic_cast<FOO>(f) ) return foo3(f,b);
throw std::runtime_error("dispatch for f not defined");
}
string foo1(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo1bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo1bar2handler(f,b);
throw std::runtime_error("foo1: dispatch for b not defined");
}
string foo2(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo2bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo2bar2handler(f,b);
throw std::runtime_error("foo2: dispatch for b not defined");
}
string foo3(foo* f, bar* b) {
if ( dynamic_cast<Bar>(b) ) return foo3bar1handler(f,b);
else if ( dynamic_cast<BAR>(b) ) return foo3bar2handler(f,b);
throw std::runtime_error("foo3: dispatch for b not defined");
}
string foo1bar1handler(foo* f, bar* b) {return "FooBar";}
string foo2bar2handler(foo* f, bar* b) {return "FooBAR";}
string foo3bar1handler(foo* f, bar* b) {return "FOoBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOoBAR";}
string foo2bar1handler(foo* f, bar* b) {return "FOOBar";}
string foo2bar2handler(foo* f, bar* b) {return "FOOBAR";}
显然,没有办法避免为我想明确处理的每个组合定义结束方法的需要。但是,我正在寻找其他方法来实现这一点。理想情况下,某些模式允许用户显式注册每个处理程序,并且任何未处理的组合都可能引发运行时异常。任何建议,将不胜感激。谢谢
一种方法(当然不是唯一的方法)是调用 foo 上的虚函数,将其传递给 bar。 foo 的每个派生类型都以相同的方式实现此调度函数,它将自身传递给 bar 中的虚拟处理程序函数。 当您需要添加更多内容时,您可以扩展接口以接受新类型。所有 foo 函数都具有相同的实现,但它们有所不同,因此 "this" 是对象的正确动态类型。
另外,Andrei Alexandrescu 在他的(现在不那么现代的)书 Modern C++ Design 中用设计替代方案对此进行了很好的调查,该书仍然涵盖了这个想法,但是为 c++98 编写,但绝对仍然值得一读(尽管它说不能做的许多事情现在是 C++ 的一部分,部分原因是那本书。)
现场观看:https://godbolt.org/z/oRyVJx
此示例有 3 个 Foo 类 和 2 个 Bar 类。
#include <iostream>
class BarBase;
class FooBase {
public:
virtual ~FooBase() = default;
virtual void dispatch(BarBase*) = 0;
};
class Foo1;
class Foo2;
class Foo3;
class BarBase {
public:
virtual ~BarBase() = default;
virtual void accept(Foo1*) = 0;
virtual void accept(Foo2*) = 0;
virtual void accept(Foo3*) = 0;
};
class Bar1 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Bar2 : public BarBase {
public:
void accept(Foo1*) override;
void accept(Foo2*) override;
void accept(Foo3*) override;
};
class Foo1 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo2 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
class Foo3 : public FooBase {
public:
void dispatch(BarBase* bar) override { bar->accept(this); }
};
void Bar1::accept(Foo1 * f) { std::cout << "Bar1 accepting Foo1\n"; }
void Bar1::accept(Foo2 * f) { std::cout << "Bar1 accepting Foo2\n"; }
void Bar1::accept(Foo3 * f) { std::cout << "Bar1 accepting Foo3\n"; }
void Bar2::accept(Foo1 * f) { std::cout << "Bar2 accepting Foo1\n"; }
void Bar2::accept(Foo2 * f) { std::cout << "Bar2 accepting Foo2\n"; }
void Bar2::accept(Foo3 * f) { std::cout << "Bar2 accepting Foo3\n"; }
//
// Doesn't know which types of foo and bar it has, but it doesn't matter...
//
void call(FooBase& foo, BarBase& bar) {
foo.dispatch(&bar);
}
int main() {
Foo1 f1;
Foo2 f2;
Foo3 f3;
Bar1 b1;
Bar2 b2;
call(f1, b1);
call(f2, b1);
call(f3, b1);
call(f1, b2);
call(f2, b2);
call(f3, b2);
}
输出:
Bar1 accepting Foo1
Bar1 accepting Foo2
Bar1 accepting Foo3
Bar2 accepting Foo1
Bar2 accepting Foo2
Bar2 accepting Foo3