微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

自动将父类中的 shared_ptr 向下转换为子类类型

如何解决自动将父类中的 shared_ptr 向下转换为子类类型

我有一个虚拟父类,用于收集带有相关报告结构的报告。报告最终应呈现为 JSON 字符串,因此我使用 https://github.com/nlohmann/json 来帮助我。

我创建了不同的不同子类来生成相应子报表结构的此类报表,挑战在于每个子报表可能具有略有不同的字段,但会从父类继承一些字段。我有将结构转换为 JSON 表示所需的宏,按报告类型定义。这是到目前为止的代码

/**
 * Compile with nlohmann json.hpp
 */

#include <iostream>
#include <vector>
#include <memory>

#include "json.hpp"
using json = nlohmann::json;

struct Report {
  // make abstract
  virtual ~Report() {}
  std::string type = "main_report";
  int foo = 0;
};

struct ChildAReport : public Report {
  std::string type = "child_a_report";
  int bar = 1;
};

struct ChildBReport : public Report {
  std::string type = "child_b_report";
  int baz = 2;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ChildAReport,type,foo,bar)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ChildBReport,baz)

class Parent {
protected:
  std::vector<std::shared_ptr<Report>> reports;
  virtual void run() = 0;
  virtual std::string get_report() = 0;
};

class ChildA : public Parent {
public:
  virtual void run() override
  {
    ChildAReport r;
    r.foo = 1;
    r.bar = 2;
    reports.push_back(std::make_shared<ChildAReport>(r));
  }

  std::string get_report() override {
    std::shared_ptr<Report> r = reports.back();
    std::shared_ptr<ChildAReport> cr = std::dynamic_pointer_cast<ChildAReport>(r);
    json r_json = *cr;
    return r_json.dump();
  }
};

class ChildB : public Parent {
public:
  virtual void run() override
  {
    ChildBReport r;
    r.foo = 1;
    r.baz = 3;
    reports.push_back(std::make_shared<ChildBReport>(r));
  }

  std::string get_report() override {
    std::shared_ptr<Report> r = reports.back();
    std::shared_ptr<ChildBReport> cr = std::dynamic_pointer_cast<ChildBReport>(r);
    json r_json = *cr;
    return r_json.dump();
  }
};

int main(int argc,char *argv[])
{
  ChildA ca = ChildA();
  ca.run();
  std::cout << ca.get_report() << std::endl;

  ChildB cb = ChildB();
  cb.run();
  std::cout << cb.get_report() << std::endl;
}

代码是用 json.hpp 编译的,没有进一步的依赖。

我期待这个输出

{"bar":2,"foo":1,"type":"child_a_report"}
{"baz":3,"type":"child_b_report"}

现在,为了实际生成 JSON,我使用了 get_report() 方法。我了解到我必须将 downcast the pointerReport 结构到实际的 ChildAChildB 结构,否则它将无法正确转换为 JSON。这很乏味;如您所见,代码在每个可能的子类中几乎是逐字重复的。 run() 函数不是问题 - 这里是各种魔法发生的地方,每个类都不同。

有没有一种方法可以将其拉到父类中,而无需在转换为 JSON 之前明确指定要进行的类型转换?理想情况下,这可以根据运行 get_report() 的实际类型来推断......

解决方法

还有一个想法是让父类成为模板类,它使用子报表类型作为模板参数:

#include <iostream>
#include <vector>
#include <memory>

struct Report {
  // make abstract
  virtual ~Report() {}
  std::string type = "main_report";
  int foo = 0;
};

struct ChildAReport : public Report {
  std::string type = "child_a_report";
  int bar = 1;
};

struct ChildBReport : public Report {
  std::string type = "child_b_report";
  int baz = 2;
};

template<typename REPORT>
class Parent 
{
public:
  std::string get_report()
  {
    auto r = reports.back();
    return r->type;
  }
  virtual void run() = 0;

protected:
  std::vector<std::shared_ptr<REPORT>> reports;
};

class ChildA : public Parent<ChildAReport> {
public:
  virtual void run() override
  {
    ChildAReport r;
    r.foo = 1;
    r.bar = 2;
    reports.push_back(std::make_shared<ChildAReport>(r));
  }
};

class ChildB : public Parent<ChildBReport> {
public:
  virtual void run() override
  {
    ChildBReport r;
    r.foo = 1;
    r.baz = 3;
    reports.push_back(std::make_shared<ChildBReport>(r));
  }
};

int main()
{
  auto ca = ChildA();
  ca.run();
  std::cout << ca.get_report() << std::endl;

  auto cb = ChildB();
  cb.run();
  std::cout << cb.get_report() << std::endl;

  return 0;
}

编辑答案,因为不再需要纯虚方法

更新您的评论:

为要在示例中使用的父类创建一个基类(接口):

class IGrandparent
{
public:
  virtual std::string get_report() = 0;
  virtual void run() = 0;
};

template<typename REPORT>
class Parent : public IGrandparent
{
public:
  std::string get_report() override 
  {
    auto r = reports.back();
    return r->type;
  }
protected:
  std::vector<std::shared_ptr<REPORT>> reports;
};

int main()
{
    std::unique_ptr<IGrandparent> childAInDisguise = std::make_unique<ChildA>();
    std::unique_ptr<IGrandparent> childBInDisguise = std::make_unique<ChildB>();

    childAInDisguise->run();
    std::cout << childAInDisguise->get_report() << std::endl;

    childBInDisguise->run();
    std::cout << childBInDisguise->get_report() << std::endl;

    return 0;
}
,

您可以做的一件事是在基类 Parent 中有一个模板,但这意味着删除 virtual 关键字,因此 Parent 类将不再是接口。这只是使 get_report 不那么乏味的简单方法,如果 Parent 类必须是接口,则此解决方案无效。

如果这对你有帮助,它会是这样的:

class Parent {
protected:
  std::vector<std::shared_ptr<Report>> reports;
  virtual void run() = 0;
  template <class T>
  std::string get_report_base() 
  {
    std::shared_ptr<Report> r = reports.back();
    std::shared_ptr<T> cr = std::dynamic_pointer_cast<T>(r);
    json r_json = *cr;
    return r_json.dump();
  }
};

孩子们会调用 get_report_base:

class ChildA : public Parent {
public:
  virtual void run() override
  {
    ChildAReport r;
    r.foo = 1;
    r.bar = 2;
    reports.push_back(std::make_shared<ChildAReport>(r));
  }

  std::string get_report()  {
    return get_report_base<ChildAReport>();
  }
};

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。