自动将父类中的 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
结构到实际的ChildA
或ChildB
结构,否则它将无法正确转换为JSON。这很乏味;如您所见,代码在每个可能的子类中几乎逐字重复。 run()
函数不是问题所在——这就是各种魔法发生的地方,每个类都不同。
有没有一种方法可以将它拉到父类,而不必在转换为 JSON 之前明确指定要进行的转换类型?理想情况下,这可以根据get_report()
正在运行的实际类型来推断...
【问题讨论】:
我认为您正在查看visitor pattern。这将避免指针的转换。 啊,我承认我在寻求一个我心目中的特定解决方案,而不是考虑完全不同的架构选择。不过,访问者模式似乎有点复杂。我仍然需要为每个执行导出的子类创建一个新方法,不是吗? (类似于visitCircle
与 visitRectangle
等)
【参考方案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<Parent>
,并在运行时使用std::make_unique<ChildA>();
实例化它,具体取决于所选的类型。现在这不再起作用了,因为当我分配std::make_unique<ChildA<ChildAReport>>();
时,它在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 向下转换为子类类型的主要内容,如果未能解决你的问题,请参考以下文章