【问题标题】:Dynamic dispatch in C++ - better syntaxC++ 中的动态调度 - 更好的语法
【发布时间】:2016-10-09 16:10:53
【问题描述】:

我想在 C++ 中有一个 dynamic_call 功能。它应该触发重载决议并根据参数的动态类型调用目标类(访问者)上最具体的函数。它应该替换访问者模式,并且应该像 C# 中的动态关键字一样工作。 我把到目前为止我得到的东西贴在下面。我不想在调用方声明通用 lambda,而是在动态调用的实现上声明它以使其更易于使用。这可能吗?

#include <iostream>

struct Base { virtual ~Base() = default; };
class A : public Base {};
class B : public Base {};

template <class... Ts>
class dynamic_call {
public:
    template <class F, class Arg>
    static void call(F& func, Arg& a) {
        call_impl<F, Arg, Ts...>(func, a);
    }
private:
    template <class F, class Arg>
    static void call_impl(F& /*func*/, Arg& /*a*/) {
        //end of recursion => nothing more to be done
    }
    template <class F, class Arg, class T, class... R>
    static void call_impl(F& func, Arg& a) {
        T* t = dynamic_cast<T*>(&a);
        if(t) {
            func(*t);
        }
        call_impl<F, Arg, R...>(func, a);
    }
};

using namespace std;
struct Visitor {
    void Visit(A&) { cout << "visited for a" << endl; }
    void Visit(B&) { cout << "visited for b" << endl; }
};

int main(int /*argc*/, char */*argv*/[])
{
    Visitor v;
    auto func = [&v](auto& a) { v.Visit(a); };

    A a;
    dynamic_call<A, B>::call(func, a);

    B b;
    dynamic_call<A, B>::call(func, b);

    {
        Base& base(a);
        dynamic_call<A, B>::call(func, base);
    }
    {
        Base& base(b);
        dynamic_call<A, B>::call(func, base);
    }

    return 0;
}

我想这样调用它而不需要添加通用 lambda。

dynamic_call<A,B>::call(v, a);

【问题讨论】:

  • 使用std::function 可能。你的问题不清楚
  • 我是否理解正确,您基本上希望Visit(或对它的包装调用)表现得像Base 的虚拟成员函数?关于您的最后一个要求:将Visitor 中的Visit 更改为operator(),您的线路将正常工作。
  • @eichhörnchen 不,我想根据参数的运行时类型调用正确的访问实现。这不是普通的虚拟通话。
  • 虚拟调用根据this的运行时类型选择被覆盖的方法,即如果VisitBase中的虚拟方法并在AB中被覆盖,没有参数,然后a.Visit()b.Visit()base.Visit() 的行为与您的 dynamic_call 示例完全相同。
  • 是否将Visit 替换为operator(),将func(*t) 替换为func.Visit(*t),在Visit 内部进行调度,或者将调度调用包装在一个额外的成员函数/结构中没有选项?

标签: c++ dynamic lambda c++14


【解决方案1】:

这里有一些想法,我不确定你的要求是什么,所以它们可能不适合:

  1. Visit 更改为operator()。然后调用语法根据您的需要减少到dynamic_call&lt;A,B&gt;::call(v, a);。当然,这只有在访问者的界面可以改变时才有可能。

  2. call_impl 中的func(*t) 更改为func.Visit(*t)。然后调用者再次可以使用dynamic_call&lt;A,B&gt;::call(v, a); 并且无需更改访问者的界面。然而,每个使用 dynamic_call 的访问者现在都需要将 Visit 定义为访问者方法。我认为operator() 更干净并且遵循通常的模式,例如更多标准库中的谓词。

我不是特别喜欢其中任何一个,因为调用者总是必须知道访问者中可能存在的重载,并且必须记住使用dynamic_call。所以我建议解决访问者结构中的所有问题:

  1. struct Visitor {
        void Visit(A&) {
            cout << "visited for a" << endl;
        }
        void Visit(B&) {
            cout << "visited for b" << endl;
        }
        void Visit(Base& x) {
            dynamic_call<A,B>::call([this](auto& x){Visit(x);}, x);
        }
    };
    

    这可以用v.Visit(a)v.Visit(b)v.Visit(base) 调用。这样Visitor 的用户就不需要了解不同派生类的不同行为。

  2. 如果你不想修改Visitor,那么你可以通过继承添加重载:

    struct DynamicVisitor : Visitor {
        void Visit(Base& x) {
            dynamic_call<A,B>::call([this](auto& x){Visit(x);}, x);
        }
    };
    

这些点可以组合,例如:

struct Visitor {
    void operator()(A&) {
        cout << "visited for a" << endl;
    }
    void operator()(B&) {
        cout << "visited for b" << endl;
    }
    void operator()(Base& x) {
        dynamic_call<A,B>::call(*this, x);
    }
};

用作v(a)v(b)v(base)

【讨论】:

  • 这绝对很有趣
  • 花了我一些时间来弄清楚这是如何工作的,但这就是我正在搜索的内容
猜你喜欢
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-12
  • 2012-06-20
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
相关资源
最近更新 更多