从对基类的引用中调用更具体的重载函数

Calling the more-specific overloaded function from a reference to a base class

本文关键字:重载 函数 基类 引用 调用      更新时间:2023-10-16

首先,对于这个神秘的标题,很抱歉,这不容易解释。

我想做的是使用重载函数在C++中实现访问者模式。这就是我的处境:

  1. 我有一组解析器。每个解析器返回一个特定的派生类型的Element。基本上,我最终得到了Elements的多态集合,所有这些集合都实现了visit(Visitor&)函数
  2. 我有一些分析仪(Visitors)。每个访问者只对几个特定的Element派生类感兴趣
  3. Visitor的基类具有visit(Element&)的空实现,该实现接收作为参数的Element引用
  4. 每个Visitor派生类为它感兴趣的特定元素类型实现visit函数。也就是说,我在DerivedVisitor类中有一个visit(DerivedElement&)重载函数
  5. 当调用accept(Visitor& v) { v.visit(*this); }时,被调用的函数应该是更具体的函数。也就是说,如果vDerivedVisitor,而accept是在DerivedElement中实现的,我希望调用函数visit(DerivedElement&)

一些示例代码:

#include <iostream>
using namespace std;
class Visitor
{
public:
virtual void visit(class BaseElement& e);
};
class BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BaseElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BaseElement" << endl;
}
};
void Visitor::visit(BaseElement& e)
{
cout << "visit on Visitor" << endl;
e.doThings();
}
class DerivedElement : public BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on DerivedElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on DerivedElement" << endl;
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement& e)
{
cout << "visit-BaseElement on DerivedVisitor" << endl;
e.doThings();
}
void visit(DerivedElement &e)
{
cout << "visit-DerivedElement on DerivedVisitor" << endl;
e.doThings();
}
};
int main(int argc, char** argv)
{
BaseElement eBase;
DerivedElement eDeriv;
BaseElement& eDerivAsBase = eDeriv;
Visitor vBase;
DerivedVisitor vDeriv;
cout << "Visiting a BaseElement with the base visitor:" << endl;
eBase.accept(vBase);
cout << endl << "Visiting a BaseElement with the derived visitor:" << endl;
eBase.accept(vDeriv);
cout << endl << "Visiting Base and Derived elements with the derived visitor" << endl;
eBase.accept(vDeriv);
eDeriv.accept(vDeriv);
cout << endl << "Visiting Base element as Derived reference" << endl;
eDerivAsBase.accept(vBase);
eDerivAsBase.accept(vDeriv);
}

这是输出

Visiting a BaseElement with the base visitor:
accept on BaseElement
visit on Visitor
doThings on BaseElement
Visiting a BaseElement with the derived visitor:
accept on BaseElement
visit-BaseElement on DerivedVisitor
doThings on BaseElement
Visiting Base and Derived elements with the derived visitor
accept on BaseElement
visit-BaseElement on DerivedVisitor
doThings on BaseElement
accept on DerivedElement
visit-BaseElement on DerivedVisitor (!)
doThings on DerivedElement
Visiting Base element as Derived reference
accept on DerivedElement
visit on Visitor
doThings on DerivedElement
accept on DerivedElement
visit-BaseElement on DerivedVisitor (!)
doThings on DerivedElement

标有(!)的行是我想要更改的行。这些行应该是"在DerivedVisitor上访问DerivedElement"。

这可能吗?看到C++没有实现多重调度,这似乎很困难,我可能在要求不可能的事情。然而,我真的很想看看我有什么替代方案,因为为我拥有的每个派生元素编写空的accept(DerivedElementN&)方法似乎不是最好的选择。

您在这里做了大量的动态间接寻址。所以你也需要用这种方式来构建你的访客。

struct BaseVisitor {
std::unordered_map<std::type_info, std::function<void(BaseElement&)>> types;
template<typename D, typename F> void AddOverload(F f) {
types[typeid(D)] = [=](BaseElement& elem) {
f(static_cast<D&>(elem));
};
}
virtual void visit(BaseElement& elem) {
if (types.find(typeid(elem)) != types.end())
types[typeid(elem)](elem);
}
}; 
struct DerivedVisitor : BaseVisitor {
DerivedVisitor() {
AddOverload<DerivedElement>([](DerivedElement& e) {
});
//... etc
}
};

核心问题是,只要需要动态间接寻址,就不能使用模板。你所能做的就是在垃圾邮件dynamic_cast上提供一层额外的类型安全性和便利性(以及潜在的速度)。

简单地说,上面的代码可能不太正常——使用引用的typeid或const之类的东西会很有趣,这可能会导致类型查找在应该成功的时候失败。

如果这对你来说很重要,还有其他技术可以应用,可以消除这种限制,但你可能想坚持使用dynamic_cast,因为它很搞笑,但很糟糕。

建议

将你的帖子标题改为"如何利用访问者模式实现专业化"。

注意:您忘记在DerivedVisitor类中使用virtual关键字。

回答

您使用的两个功能不能很好地混合:

方法重写("virtual")。其中,来自相关类的方法和相同的id具有相同的参数。

class Visitor
{
public:
virtual void visit(BaseElement& e)
{
// print element value to console
}
};
class DerivedVisitor : public Visitor
{
public:
virtual void visit(BaseElement& e)
{
// save element value into file
}
};

方法过载。其中,来自相关类的方法方法和相同的id具有不同的参数。

class Visitor
{
public:
void visit(BaseElement& e)
{
// ...
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement& e)
{
// ...
}
void visit(DerivedElement& e)
{
// ...
}
};

由于您没有显示足够的源代码,我假设您将参数作为Element&传递,即使它是作为DerivedElement&创建的,所以编译器调用void visit(BaseElement& e),而不是void visit(DerivedElement& e)

我还注意到,您使用的是"引用",而不是指针。编译器可能将您的DerivedElement强制转换为BaseElement。我建议使用"指针"。

在使用多态性时,最好不要使用"方法重载",而是坚持使用"方法重写",因为编译器总是调用"虚拟"或"重写"方法。

class BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BaseElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BaseElement" << endl;
}
};
class FooElement: BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on FooElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on FooElement" << endl;
}
};
class BarElement: BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BarElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BarElement" << endl;
}
};
class Visitor
{
public:
void visit(BaseElement* e)
{
cout << "visit Any Element on Visitor" << endl;
e.doThings();
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement* e)
{
cout << "visit Any Element on DerivedVisitor" << endl;
e.doThings();
}
};
int main (...)
{
BaseElement* e1 = new FooElement();
BaseElement* e2 = new BarElement();
BaseVisitor* v = new DerivedVisitor();
v->visit(e1);
v->visit(e2);
free v();
free e2();
free e1();
} // int main (...)

注:我跳过了大部分访问者操作,只是为了简化重载操作。

更新:通过方法重写(A.K.A."virtual"关键字),专门化在每个Element类代码处,而不是在Visitor类代码处。从每个派生类调用匹配方法。

干杯。