使用具有多态行为的重载函数

using overloaded functions with polymorphic behaviour

本文关键字:重载 函数 多态      更新时间:2023-10-16

我正在实现一个执行特定于字段的任务的通用工作引擎类。所有字段都应派生自基类,因此将使用多态行为。我创建了特定于派生字段类型的重载函数,即work_engine::run(const a_field& af)在下面的代码中。我假设每当出现字段(基类类型(时,都会自动调用相应的函数。但是,我在下面遇到了错误(参考//魔术行(:

Error   C2664    'void work_engine::run(const b_field &)': cannot convert argument 1 from 'base_field' to 'const a_field &' 

我曾经在 C# 中使用过这种方法,但在C++中我不熟悉它。通过使用 C++11 及更高版本的功能,我想实现相同的方法,因为它比将 if-else 语句与强制转换操作一起使用更干净的代码。另一方面,也许我犯了原始错误,所以我想把我的问题分成两个项目:

  1. 实现我的意图的正确方法是什么?
  2. 我遇到的错误的起源是什么?

提前感谢您的所有评论,

包含类定义的 header1.h 如下:

#pragma once
#include <algorithm>
#include <list>
// abstract class of all fields 
class base_field
{
public:
base_field() {}
virtual ~base_field() {}
};
// custom a field
class a_field : public base_field
{
public:
a_field() : base_field(){}
};
// custom b field
class b_field : public base_field
{
public:
b_field() : base_field() {}
};
class work_engine
{
public:
std::list<base_field> fields;
private:
void run(const a_field& af) {}
void run(const b_field& bf){}
public:
void run_all()
{
for_each(fields.begin(), fields.end(), [&](auto& el) { this->run(el); }); // magic line
}
};

主要如下:

#include <iostream>
#include <Header1.h>
int main()
{
work_engine engine;
engine.fields.push_back(a_field());
engine.fields.push_back(b_field());
engine.run_all();
}

换句话说,我正在寻找的是隐式转换为 lambda 表达式中的具体类,指的是//魔术行。

首先:您需要一个本问题中详述的std::list<base_field *>

其次,必须显式执行下垂转换,并且不能在运行时自动选择适当的函数重载。


一种解决方案可能是在base_field上使用(纯(虚函数

class base_field
{
public:
base_field() {}
virtual ~base_field() {}
virtual void Run() = 0;
};
// custom a field
class a_field : public base_field
{
public:
a_field() : base_field(){}
void Run(work_engine &engine) { engine.run(*this); }
};

在那里,您要么需要公开work_engine::run要么将其设为a_field的朋友(或恭敬地b_field(。


另一种方法是在运行时使用类型检查。如果您有base_field *base可以像这样检查类型:

auto aPtr = dynamic_cast<a_field *>(base);
if(aPtr != nullptr) {
this->run(*aPtr);
}
else {
auto bPtr = dynamic_cast<b_field *>(base);
if(bPtr != nullptr) {
this->run(*bPtr);
}
else {
// Ooops, it's something else entirely.    
}
}

这只是一个基本的大纲,但我希望它有所帮助。

std::vector<std::unique_ptr<base_field>> fields;

如果你想要多态性,请不要使用值。 此外,std 列表是C++中的一个特例容器;默认使用矢量。

struct a_field;
struct b_field;
struct field_visitor{
virtual void operator()(a_field const&)=0;
virtual void operator()(b_field const&)=0;
};
template<class F>
struct visitor:field_visitor{
F f;
visitor(F in):f(in){}
virtual void operator()(a_field const& a){ f(a); };
virtual void operator()(b_field const& b){ f(b); };
};

class base_field {
public:
virtual void visit(field_visitor&)const=0;
// ...
class a_field : public base_field {
public:
virtual void visit(field_visitor& v){v(*this);}
//...
class b_field : public base_field {
public:
virtual void visit(field_visitor& v){v(*this);}
//...
void run_all() {
for_each(fields.begin(), fields.end(), [&](auto&& el) { if(el) el->visit( visitor{ [&](auto& field){this->run(field); } });});
}

还有很多其他方法可以做到这一点。(这个是 c++17,但采用 c++03 风格(。

另一种选择是存储variant<a_field, b_field>std::visit它。 另一种选择是编写runnable类型的擦除类型。

std::vector<std::variant<a_field, b_field>> fields;
//...
void run_all() {
for_each(fields.begin(), fields.end(), [&](auto&& el) { std::visit( el, [&](auto&& x){ this->run(x); } ); });
}

变体版本值得注意的是,a_fieldb_field不必是相关类型,只需run必须接受它们即可。 所以一个可以std::string另一个double.


C++ 在运行时或链接时(通常(不会携带编译器,因此使用点的代码必须知道它是为什么类型编写的。 Vtable 调度采用单向方式,并且不会由于类定义之外的请求而扩展。

相比之下,C#随身携带一个编译器;因此它可以基于两个单独的代码段自动添加run动态调度。

在C++中,在双重调度中,应列出一组已处理的子类型。 在单个调度中,需要在类型内完成。

有一些方法可以扩展它,但不能像 C# 那样远。

我看到使用值而不是指针会导致对象切片:我用指针更改了值,shared_ptr现代C++。第二个,我将 run 函数移动到每个字段中,因此特定于字段的函数与相关字段封装在一起。

#pragma once
#include <algorithm>
#include <list>
// abstract class of base fields 
class base_field
{
public:
base_field() {}
virtual ~base_field() {}
virtual void run() = 0;
};
// custom a field
class a_field : public base_field
{
public:
a_field() : base_field(){}
virtual void run() override{}
};
// custom b field
class b_field : public base_field
{
public:
b_field() : base_field() {}
virtual void run() override {}
};
class work_engine
{
public:
std::list<std::shared_ptr<base_field>> fields;
private:
public:
void run_all()
{
for_each(fields.begin(), fields.end(), [&](auto& el) { el->run(); });
}
};

通过这些更改,错误将消失并按预期工作。