【问题标题】:Double Dispatch in C++C++ 中的双重调度
【发布时间】:2020-06-03 03:24:32
【问题描述】:

我需要以下类型的调度函数。在我的应用程序中,我真正拥有的是一个指向状态基类 (foo) 的指针以及一个指向测量基类 (bar) 的指针。根据传递给 dispatch2 函数的派生实例,我需要在给定状态的情况下生成估计的测量值。例如,派生的状态类可以是位置,而派生的测量类可以是 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";}

显然,没有办法绕过为我想明确处理的每个组合定义结束方法的需要。但是,我正在寻找实现这一点的替代方法。理想情况下,一些允许用户显式注册每个处理程序的模式,并且任何未处理的组合都可能引发运行时异常。任何建议,将不胜感激。谢谢

【问题讨论】:

  • 我使用了映射,其中键是对,值是要调用的函数,其中无符号值是调用 typeinfo::hash() 的结果。抛出异常或其他行为很容易,这只是当映射没有与哈希值对匹配的键时所做的。
  • 看看访客模式。

标签: c++ design-patterns dispatch


【解决方案1】:

一种方法(当然不是唯一的方法)是在 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

【讨论】:

  • 这很好用。我修改了您的示例以在基类中提供默认实现方法,以防所有排列都未在派生类中处理,工作得很好。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多