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

Posted

技术标签:

【中文标题】自动将父类中的 shared_ptr 向下转换为子类类型【英文标题】:Automatically downcast a shared_ptr in parent class to child class type 【发布时间】:2021-09-12 13:44:32 【问题描述】:

我有一个虚拟父类,用于收集报告及其关联的报告结构。报告最后应该呈现为 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, 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 pointer 转换为Report 结构到实际的ChildAChildB 结构,否则它将无法正确转换为JSON。这很乏味;如您所见,代码在每个可能的子类中几乎逐字重复。 run() 函数不是问题所在——这就是各种魔法发生的地方,每个类都不同。

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

【问题讨论】:

我认为您正在查看visitor pattern。这将避免指针的转换。 啊,我承认我在寻求一个我心目中的特定解决方案,而不是考虑完全不同的架构选择。不过,访问者模式似乎有点复杂。我仍然需要为每个执行导出的子类创建一个新方法,不是吗? (类似于 visitCirclevisitRectangle 等) 【参考方案1】:

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

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

【讨论】:

有趣!我还需要为每个子类提供std::string get_report() override 的实现吗?还是不能把它移到一个父类函数中? 是的,你是对的,那就更好了,没注意到! @slhck 在我看来,糟糕的设计也是改变虚拟方法的访问说明符。您将方法声明为受保护并将它们覆盖为公共。将它们在基类中公开,因为您希望它们可以从外部使用 现在,假设从外部来看,我曾经有一个std::unique_ptr&lt;Parent&gt;,并在运行时使用std::make_unique&lt;ChildA&gt;(); 实例化它,具体取决于所选的类型。现在这不再起作用了,因为当我分配std::make_unique&lt;ChildA&lt;ChildAReport&gt;&gt;(); 时,它在unique_ptr 上显示“缺少类模板的参数列表”。我将如何修改它? 我想通了。我需要提供一个不依赖模板并从中派生的虚拟BaseParent 类。谢谢你的回答!【参考方案2】:

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

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

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

【讨论】:

以上是关于自动将父类中的 shared_ptr 向下转换为子类类型的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从父类指针向下转换为第二个子类中的子类指针?

java中可以将父类对象强制转换为子类对象吗?直接将父类对象强制转换为子类对象,与将上转型对象强制

用父类引用指向子类好处

面对对象-多态

Java面向对象多态和接口

来自一个小菜鸟的总结--接口