【问题标题】:using overloaded functions with polymorphic behaviour使用具有多态行为的重载函数
【发布时间】:2019-12-20 11:33:49
【问题描述】:

我正在实现一个执行特定领域任务的通用工作引擎类。所有字段都应从基类派生,因此将使用多态行为。我创建了具有特定于派生字段的类型的重载函数,即以下代码中的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. 我遇到的错误的根源是什么?

提前感谢您的所有 cmets,

包含类定义的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 表达式中的具体类,指的是 // 魔术线。

【问题讨论】:

  • 这能回答你的问题吗? Vectors and polymorphism in C++
  • 实际上并非如此,我的问题更多是关于在 lambda 表达式中隐式转换为具体类。 // 魔法线
  • 这是你的问题。在 C++ 中,这是具体类型的实例列表,而不是多态类型的引用列表。后半部分是您需要访问者模式,可以手动、通过变体或自己编写一些机器。

标签: c++ c++11 inheritance polymorphism overloading


【解决方案1】:

首先:您需要std::list&lt;base_field *&gt;,详情请参阅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 设为公开,要么将其设为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.    
    }
}

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

【讨论】:

    【解决方案2】:
    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); } });});
    }
    

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

    另一种选择是存储 variant&lt;a_field, b_field&gt;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# 那样。

    【讨论】:

      【解决方案3】:

      我看到使用值而不是指针会导致对象切片:我用指针更改了值,现代 C++ 中的 shared_ptr。第二,我把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(); });
          }
      };
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多