使用具有多态行为的重载函数
using overloaded functions with polymorphic behaviour
我正在实施一个通用工作引擎 class 来执行特定领域的任务。所有字段都应从基础 class 派生,因此将使用多态行为。我创建了具有派生字段特定类型的重载函数,即下面代码中的 work_engine::run(const a_field& af)
。我假设无论何时出现一个字段(基础 class 类型),都会自动调用适当的函数。但是,我得到以下错误(参考 // magic line):
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 语句更清晰的代码。另一方面,也许我犯了原始错误,所以我想把我的问题分成两部分:
- 实现我的意图的正确方法是什么?
- 我遇到的错误的根源是什么?
提前感谢您的所有评论,
Header1.h 包括 class 定义如下:
#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 表达式中隐式转换为具体的 class,指的是 // magic 行。
首先:您需要 std::list<base_field *>
,详见 this question。
其次,向下转换必须显式完成,您不能在运行时自动选择适当的函数重载。
一种解决方案是在 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
添加为 public 或将其添加为 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 list 也是 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 but in a 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_field
和 b_field
不需要是相关类型,只需 run
必须接受它们。所以一个可以是 std::string
另一个 double
.
C++ 在 运行 或 link 时(通常)不携带编译器,所以代码在使用时必须知道它是为什么类型编写的。 Vtable 调度是单向的,并且不会由于 class 定义之外的请求而扩展。
相比之下,C# 自带编译器;因此它可以自动添加一个 run
基于两段独立代码的动态调度。
在 C++ 中,在双重分派中,应列出已处理的子类型集之一。而在single dispatch中,需要在类型内完成。
有一些方法可以扩展它,但不如 C#。
我发现使用值而不是指针会导致对象切片:我用指针更改了值,shared_ptr 在现代 C++ 中。第二,我将 运行 函数移动到每个字段中,因此特定于字段的函数与相关字段一起封装。
#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(); });
}
};
通过这些更改,错误消失并按预期工作。
我正在实施一个通用工作引擎 class 来执行特定领域的任务。所有字段都应从基础 class 派生,因此将使用多态行为。我创建了具有派生字段特定类型的重载函数,即下面代码中的 work_engine::run(const a_field& af)
。我假设无论何时出现一个字段(基础 class 类型),都会自动调用适当的函数。但是,我得到以下错误(参考 // magic line):
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 语句更清晰的代码。另一方面,也许我犯了原始错误,所以我想把我的问题分成两部分:
- 实现我的意图的正确方法是什么?
- 我遇到的错误的根源是什么?
提前感谢您的所有评论,
Header1.h 包括 class 定义如下:
#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 表达式中隐式转换为具体的 class,指的是 // magic 行。
首先:您需要 std::list<base_field *>
,详见 this question。
其次,向下转换必须显式完成,您不能在运行时自动选择适当的函数重载。
一种解决方案是在 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
添加为 public 或将其添加为 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 list 也是 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 but in a 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_field
和 b_field
不需要是相关类型,只需 run
必须接受它们。所以一个可以是 std::string
另一个 double
.
C++ 在 运行 或 link 时(通常)不携带编译器,所以代码在使用时必须知道它是为什么类型编写的。 Vtable 调度是单向的,并且不会由于 class 定义之外的请求而扩展。
相比之下,C# 自带编译器;因此它可以自动添加一个 run
基于两段独立代码的动态调度。
在 C++ 中,在双重分派中,应列出已处理的子类型集之一。而在single dispatch中,需要在类型内完成。
有一些方法可以扩展它,但不如 C#。
我发现使用值而不是指针会导致对象切片:我用指针更改了值,shared_ptr 在现代 C++ 中。第二,我将 运行 函数移动到每个字段中,因此特定于字段的函数与相关字段一起封装。
#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(); });
}
};
通过这些更改,错误消失并按预期工作。