将 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 pointer 到 Report
结构到实际的 ChildA
或 ChildB
结构,否则它不会正确转换为 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>();
}
};
我有一个虚拟 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 pointer 到 Report
结构到实际的 ChildA
或 ChildB
结构,否则它不会正确转换为 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>();
}
};