"Dynamic"没有模板或向下投射的参数类型(?

"Dynamic" type of argument without template or downcast (?)

本文关键字:参数 类型 Dynamic      更新时间:2023-10-16

我有我的fancyFunction,它需要一组实现接口A的元素。该函数根据通过接口读取的属性对这些元素进行复杂的分析A。在此分析过程中,它将调用将元素作为参数的Consumer c的方法。

Consumer旨在采用与A完全无关的特定类型的参数。

你可以想象A是图形中边缘的抽象。图形以fancyFunction进行分析,例如,每次函数"越过"一条边时,它都会将该边发送到一个Consumer,该打印存储在边缘中的附加信息,这些信息与它是一条边无关。

下面给出的代码当然不会用类型语言(特别是C++)编译,但省略类型(Matlab,Python),代码就可以工作。

为了使它以键入的语言(特别是C++)工作,我看到了两个选项:

  1. 将函数声明为

    template <class CONSUMER> void fancyFunction(A[] setOfAs, CONSUMER c){ ... }

  2. 声明 operation1operation2 来获取最通用的对象,然后在实现中执行向下转换。

在这种情况下,您建议怎么做? (据我所知,访客模式不是一种选择。

完整的代码大纲(我有一段时间没有使用C++,所以如果有轻微的语法错误,请原谅。

void fancyFunction(A[] setOfAs, Consumer* c){
  // do fancy analysis of setOfAs by properties
  // read through interface A
  double x = setOfAs[i]->getX();
  // call functions in c with arguments of setOfAs[j]
  ...
  c->operationX(setOfAs[i]);
  ...
  c->operationY(setOfAs[j]);
  ...
}
class A{
  virtual double getX();
}
class Consumer{
  virtual void operationX(??? x); // whoops, what type do we expect?
  virtual void operationY(??? y); // whoops, what type do we expect?
}
class Consumer1{
  void operationX(Obj1 x){ ... } // whoops, override with different type
  void operationY(Obj1 y){ ... } // whoops, override with different type 
}
class Consumer2{
  void operationX(Obj2 x){ ... } // whoops, override with different type
  void operationY(Obj2 y){ ... } // whoops, override with different type
}
class Obj1 : public A {};
class Obj2 : public A {};
void test(){
    Obj1 o1[];
    Obj2 o2[];
    Callback1 c1;
    Callback2 c2;
    fancyFunction(o1, &c1);
    fancyFunction(o2, &c2);
}
我相信

您正在寻找的解决方案称为访客模式。

您不想在花哨的函数中手动转换对象 A 的每个实例,因为这是维护的噩梦和清晰的代码气味

另一方面,如果每个对象都自动处理自己的强制转换怎么办? 这就是访客模式。

首先在基类 (A) 中定义一个新的 "Visit" 函数,将 Consumer 作为其唯一参数:

class A
{
public:
    virtual void Visit(Consumer& consumer) = 0;
}

然后为每个继承的类实现此函数,因此:

class B : public A
{
public:
    void Visit(Consumer& consumer)
    {
        consumer.DoOperation(this); // 'this' utomatically resolves to type B*
    }
}

现在,每个派生类型通过将"this"指针传递给提供的使用者实例来处理调用相应的操作重载。 "this"指针会自动解释为最具体的类型。

回顾原始示例代码,似乎每个使用者都提供多个操作,并且只处理一种类型。 此模式可能需要稍微更改范例:为每个操作创建一个使用者,其中每个使用者为每个可能的继承类型提供重载。

class ConsumerX
{
public:
    void DoOperation(A* a) { /* ERROR!  This is a base type.  If this function is called, you probably need to implement another overload. */ }
    void DoOperation(B* b) { /* Much better */ }
}
class ConsumerY
{
public:
    void DoOperation(A* a) { /* ERROR!  This is a base type.  If this function is called, you probably need to implement another overload. */ }
    void DoOperation(B* b) { /* Much better */ }
}

然后,您的实现循环如下所示:

ConsumerX consumerX; // Does Operation X for every type
ConsumerY consumerY; // Does Operation Y for every type
for(int x = 0; x < numElements, x++)
{
    auto element = setOfAs[x];
    element.Visit(consumerX); //Do operation X
    element.Visit(consumerY); //Do operation Y
}

显然是合适的模板。我什至会质疑为什么你的fancyFunction坚持基础类A.它应该只需要一个开始和结束迭代器。我也不会打扰消费者。也让它变得灵活,只需使用任何功能。

事实上,我什至不会写fancyFunction.它已经存在:

std::for_each(o1.begin(), o1.end(),
   [c1](Obj1 o) { double x = o.getX(); c1.operationX(o); c1.operationY(o); }
);