派生 类 的 C++ 查找 table
C++ Lookup table for derived classes
我有一个包装器 class 通过对公共基 class 的引用向量来保存一堆派生的 class 对象。在运行时,子对象是根据用户输入创建的。
#include <iostream>
#include <vector>
#include <memory>
#include <type_traits>
class Base {
public:
virtual void run() = 0;
};
class Wrapper {
public:
std::vector<std::shared_ptr<Base>> blocks;
template <class Child>
auto create() -> std::shared_ptr<Child>
{
auto block = std::make_shared < Child > ();
blocks.emplace_back(block);
return std::move(block);
}
template <typename A, typename B>
void connect(A a, B b)
{
using connectionType = typename A::element_type::out;
connectionType* port = new connectionType;
a.get()->ptr_out = port;
b.get()->ptr_in = port;
}
};
class Child_1 : public Base {
public:
using in = int;
using out = float;
out* ptr_out;
in* ptr_in;
void run() { std::cout<<"running child 1\n"; *ptr_out = 1.234;};
};
class Child_2 : public Base {
public:
using in = float;
using out = bool;
out* ptr_out;
in* ptr_in;
void run() { std::cout<<"running child 2\ngot: "<<*ptr_in; };
};
int main () {
Wrapper wrapper;
/* read config file with a list of strings of which types to create */
std::vector < std::string > userInput;
userInput.push_back("Type 0");
userInput.push_back("Type 1");
for (auto input:userInput)
{
if (input == "Type 0")
wrapper.create < Child_1 > ();
else if (input == "Type 1")
wrapper.create < Child_2 > ();
/* and so on */
}
/* read config file with a list of pairs of which objects to connect */
std::vector < std::pair < int, int >>connections;
connections.push_back(std::make_pair(0, 1));
// e.g. user wants to connect object 0 with object 1:
for (int i = 0; i < connections.size (); i++)
{
auto id0 = connections[i].first; // e.g. 0
auto id1 = connections[i].second; //e.g. 1
// this will not work because Base has no typename in / out:
// wrapper.connect (wrapper.blocks[id0], wrapper.blocks[id1]);
// workaround:
wrapper.connect(
std::dynamic_pointer_cast<Child_1>(wrapper.blocks[id0]),
std::dynamic_pointer_cast<Child_2>(wrapper.blocks[id1]));
}
wrapper.blocks[0].get()->run();
wrapper.blocks[1].get()->run();
return 0;
}
现在,我只能存储一个 Base 对象的向量,它不能保存每个派生对象的不同 in/out 类型。 当我想连接派生对象(存储为基础 class 对象)时,我需要将它们 dynamic_pointer_cast 返回到它们的派生 class。最有效的方法是什么?
我可以想到几种方法 - none 其中(据我所知)似乎可以使用 C++:
- 进行某种查找-table / 枚举,return 是要转换为的类型;然后我可以创建一个从用户输入“Type 0”等到类型的映射并进行相应的转换。
- 有某种类似于 lambda 的表达式,可以 return 正确转换指针类型,这样我就可以调用
wrapper.connect( lambda_expression(...), lambda_expression(...) )
.
- 蛮力:检查每个可能的用户输入组合,并使用 dynamic_pointer_cast 调用连接函数(如代码示例所示)。对于我的实际应用程序(目前使用大约 25 个这样的 classes),这很可能不是 suitable,因为它会导致大量不可维护的函数调用...
- 以某种方式将泛型 in/out 类型赋予基础 class,但我想不出任何方法来做到这一点。
我真的希望我遗漏了一些明显的东西。非常感谢任何帮助。
这看起来像是双重动态调度的典型案例,但是有一个可能的简化,因为输出和输入类型必须匹配。因此,这是一种 half-Visitor 模式。
首先,我们将输入和输出类型的概念提取到类中,以便它们可以被dynamic_cast
:
定位
template <class In_>
struct BaseInput {
using In = In_;
std::shared_ptr<In> ptr_in;
};
template <class Out_>
struct BaseOutput {
using Out = Out_;
std::shared_ptr<Out> ptr_out;
};
注意:我交换了 std::shared_ptr
s 而不是在野外让原始拥有指针。
从那里,我们可以在 Base
中声明一个虚 connectTo
函数来获得第一级动态调度:
class Base {
public:
virtual ~Base() = default;
virtual void run() = 0;
virtual void connectTo(Base &other) = 0;
};
注意:我在 Base
中添加了一个虚拟析构函数。 AFAICT 由于 std::shared_ptr
的类型擦除,它是多余的,但是 Clang 向我发出警告,我不愿意追逐它们。
最后,第二个动态查找可以从 Base::connectTo
的覆盖中完成,我已经在一个方便的模板中分解出来了:
template <class In, class Out>
struct Child
: Base
, BaseInput<In>
, BaseOutput<Out> {
void connectTo(Base &other_) override {
// Throws std::bad_cast if other_'s input type doesn't match our output type
auto &other = dynamic_cast<BaseInput<Out> &>(other_);
this->ptr_out = other.ptr_in = std::make_shared<Out>();
}
};
此时,访问者模式将交换 objects 并从 other_
执行第二个虚拟调用以获得第二个动态调度。然而,正如上面提到的,我们确切地知道我们正在寻找什么类型,所以我们可以 dynamic_cast
达到它。
现在我们可以简单地实现Wrapper::connect
:
template < typename A, typename B > void connect (A a, B b)
{
a.get()->connectTo(*b.get());
}
... 并这样定义 child 类:
class Child_1 : public Child<int, float> {
public:
void run() { std::cout<<"running child 1\n"; *ptr_out = 1.234;};
};
我有一个包装器 class 通过对公共基 class 的引用向量来保存一堆派生的 class 对象。在运行时,子对象是根据用户输入创建的。
#include <iostream>
#include <vector>
#include <memory>
#include <type_traits>
class Base {
public:
virtual void run() = 0;
};
class Wrapper {
public:
std::vector<std::shared_ptr<Base>> blocks;
template <class Child>
auto create() -> std::shared_ptr<Child>
{
auto block = std::make_shared < Child > ();
blocks.emplace_back(block);
return std::move(block);
}
template <typename A, typename B>
void connect(A a, B b)
{
using connectionType = typename A::element_type::out;
connectionType* port = new connectionType;
a.get()->ptr_out = port;
b.get()->ptr_in = port;
}
};
class Child_1 : public Base {
public:
using in = int;
using out = float;
out* ptr_out;
in* ptr_in;
void run() { std::cout<<"running child 1\n"; *ptr_out = 1.234;};
};
class Child_2 : public Base {
public:
using in = float;
using out = bool;
out* ptr_out;
in* ptr_in;
void run() { std::cout<<"running child 2\ngot: "<<*ptr_in; };
};
int main () {
Wrapper wrapper;
/* read config file with a list of strings of which types to create */
std::vector < std::string > userInput;
userInput.push_back("Type 0");
userInput.push_back("Type 1");
for (auto input:userInput)
{
if (input == "Type 0")
wrapper.create < Child_1 > ();
else if (input == "Type 1")
wrapper.create < Child_2 > ();
/* and so on */
}
/* read config file with a list of pairs of which objects to connect */
std::vector < std::pair < int, int >>connections;
connections.push_back(std::make_pair(0, 1));
// e.g. user wants to connect object 0 with object 1:
for (int i = 0; i < connections.size (); i++)
{
auto id0 = connections[i].first; // e.g. 0
auto id1 = connections[i].second; //e.g. 1
// this will not work because Base has no typename in / out:
// wrapper.connect (wrapper.blocks[id0], wrapper.blocks[id1]);
// workaround:
wrapper.connect(
std::dynamic_pointer_cast<Child_1>(wrapper.blocks[id0]),
std::dynamic_pointer_cast<Child_2>(wrapper.blocks[id1]));
}
wrapper.blocks[0].get()->run();
wrapper.blocks[1].get()->run();
return 0;
}
现在,我只能存储一个 Base 对象的向量,它不能保存每个派生对象的不同 in/out 类型。 当我想连接派生对象(存储为基础 class 对象)时,我需要将它们 dynamic_pointer_cast 返回到它们的派生 class。最有效的方法是什么?
我可以想到几种方法 - none 其中(据我所知)似乎可以使用 C++:
- 进行某种查找-table / 枚举,return 是要转换为的类型;然后我可以创建一个从用户输入“Type 0”等到类型的映射并进行相应的转换。
- 有某种类似于 lambda 的表达式,可以 return 正确转换指针类型,这样我就可以调用
wrapper.connect( lambda_expression(...), lambda_expression(...) )
. - 蛮力:检查每个可能的用户输入组合,并使用 dynamic_pointer_cast 调用连接函数(如代码示例所示)。对于我的实际应用程序(目前使用大约 25 个这样的 classes),这很可能不是 suitable,因为它会导致大量不可维护的函数调用...
- 以某种方式将泛型 in/out 类型赋予基础 class,但我想不出任何方法来做到这一点。
我真的希望我遗漏了一些明显的东西。非常感谢任何帮助。
这看起来像是双重动态调度的典型案例,但是有一个可能的简化,因为输出和输入类型必须匹配。因此,这是一种 half-Visitor 模式。
首先,我们将输入和输出类型的概念提取到类中,以便它们可以被dynamic_cast
:
template <class In_>
struct BaseInput {
using In = In_;
std::shared_ptr<In> ptr_in;
};
template <class Out_>
struct BaseOutput {
using Out = Out_;
std::shared_ptr<Out> ptr_out;
};
注意:我交换了 std::shared_ptr
s 而不是在野外让原始拥有指针。
从那里,我们可以在 Base
中声明一个虚 connectTo
函数来获得第一级动态调度:
class Base {
public:
virtual ~Base() = default;
virtual void run() = 0;
virtual void connectTo(Base &other) = 0;
};
注意:我在 Base
中添加了一个虚拟析构函数。 AFAICT 由于 std::shared_ptr
的类型擦除,它是多余的,但是 Clang 向我发出警告,我不愿意追逐它们。
最后,第二个动态查找可以从 Base::connectTo
的覆盖中完成,我已经在一个方便的模板中分解出来了:
template <class In, class Out>
struct Child
: Base
, BaseInput<In>
, BaseOutput<Out> {
void connectTo(Base &other_) override {
// Throws std::bad_cast if other_'s input type doesn't match our output type
auto &other = dynamic_cast<BaseInput<Out> &>(other_);
this->ptr_out = other.ptr_in = std::make_shared<Out>();
}
};
此时,访问者模式将交换 objects 并从 other_
执行第二个虚拟调用以获得第二个动态调度。然而,正如上面提到的,我们确切地知道我们正在寻找什么类型,所以我们可以 dynamic_cast
达到它。
现在我们可以简单地实现Wrapper::connect
:
template < typename A, typename B > void connect (A a, B b)
{
a.get()->connectTo(*b.get());
}
... 并这样定义 child 类:
class Child_1 : public Child<int, float> {
public:
void run() { std::cout<<"running child 1\n"; *ptr_out = 1.234;};
};