将 parent class 中的 shared_ptr 自动向下转换为 child class 类型

Automatically downcast a shared_ptr in parent class to child class type

我有一个虚拟 parent class 用于收集报告及其关联的报告结构。报告最后应该呈现为 JSON 字符串,所以我使用 https://github.com/nlohmann/json 来帮助我。

我创建了不同的 child classes 来生成相应 child 报告结构的此类报告,挑战在于每个 child 报告可能略有不同的领域,但从 parent 继承了一些。我有将结构转换为 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, type, foo, 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,"foo":1,"type":"child_b_report"}

现在,为了实际生成 JSON,我使用 get_report() 方法。我了解到我必须 downcast the pointerReport 结构到实际的 ChildAChildB 结构,否则它不会正确转换为 JSON。这很乏味;如您所见,代码几乎是逐字重复的 child class。 run() 函数不是问题所在 - 这里是各种魔法发生的地方,它在 per-class 基础上有所不同。

有没有一种方法可以将其提升到 parent class 而无需在转换为 JSON 之前明确指定要进行的转换类型?理想情况下,这可以根据 get_report() 在 ...

上 运行 的实际类型来推断

另外一个想法是使父 class 成为模板 class,它使用子报告类型作为模板参数:

#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(接口):

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;
}

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

如果这对你有帮助,那就是这样的:

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();
  }
};

而 children 将调用 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>();
  }
};