C++ 将运行时类型映射到并行类层次结构…;使用模板
我有两个具有1:1关系的类层次结构:一些普通类C++ 将运行时类型映射到并行类层次结构…;使用模板,c++,templates,c++11,type-erasure,virtual-functions,C++,Templates,C++11,Type Erasure,Virtual Functions,我有两个具有1:1关系的类层次结构:一些普通类a,B,它们有一个公共Root接口,以及一个WrapperRoot接口,其中有两个具体的实例化WrapperA和WrapperB。我现在希望实现一个函数autowrap(Root&elem)->unique_ptr,它将每个普通类映射到它的包装类 包装器的确切类型很重要,因为它们将具有虚拟方法,并且根对象的确切类型在静态上是未知的 尝试的解决方案 我的第一个想法是在Root中声明一个模板虚拟方法: class Root { ... public
a
,B
,它们有一个公共Root
接口,以及一个WrapperRoot
接口,其中有两个具体的实例化WrapperA
和WrapperB
。我现在希望实现一个函数autowrap(Root&elem)->unique_ptr
,它将每个普通类映射到它的包装类
包装器的确切类型很重要,因为它们将具有虚拟方法,并且根对象的确切类型在静态上是未知的
尝试的解决方案
我的第一个想法是在Root
中声明一个模板虚拟方法:
class Root {
...
public:
template<typename T>
virtual auto wrap() -> unique_ptr<WrapperRoot<T>> = 0;
}
不应修改main
方法,但可以添加任意方法、助手类型和wrap
函数的实现
#include <iostream>
#include <memory>
using namespace std;
// the Root hierarchy
class Root {
public:
virtual ~Root() {}
};
class A : public Root {};
class B : public Root {};
// the Wrapper hierarchy
template<typename T>
class WrapperRoot {
public:
virtual ~WrapperRoot() {}
virtual T name() = 0;
};
template<typename T>
class WrapperA : public WrapperRoot<T> {
public:
virtual T name() { return T("WrapperA\n"); }
};
template<typename T>
class WrapperB : public WrapperRoot<T> {
public:
virtual T name() { return T("WrapperB\n"); }
};
// the "wrap" function I want to implement
template<typename T>
auto wrap(Root& ) -> unique_ptr<WrapperRoot<T>>;
// util
template<typename T, typename... Args>
auto make_unique(Args... args) -> unique_ptr<T> {
return unique_ptr<T>(new T(forward<Args>(args)...));
}
int main() {
unique_ptr<Root> a = make_unique<A>();
unique_ptr<Root> b = make_unique<B>();
cout << wrap<string>(*a)->name()
<< wrap<string>(*b)->name();
}
#包括
#包括
使用名称空间std;
//根层次结构
类根{
公众:
虚拟~Root(){}
};
A类:公共根{};
B类:公共根{};
//包装层次结构
模板
类包装器根{
公众:
虚拟~WrapperRoot(){}
虚拟T名称()=0;
};
模板
类WrapperA:公共WrapperRoot{
公众:
虚拟T名称(){return T(“WrapperA\n”);}
};
模板
类WrapperB:公共WrapperRoot{
公众:
虚拟T名称(){return T(“WrapperB\n”);}
};
//我想实现的“wrap”函数
模板
自动换行(根和根)->unique\u ptr;
//利用率
模板
自动使_唯一(Args…Args)->唯一\u ptr{
返回唯一的ptr(新的T(转发(args)…);
}
int main(){
唯一的_ptra=使_唯一();
唯一的_ptrb=使_唯一();
cout name()
名称();
}
我怎样才能做到这一点?或者我需要求助于违反类型的黑客吗?让它工作的最简单的方法就是动态\u cast
调用根和以确定它的运行时类型:
template<typename T>
auto wrap(Root& root) -> unique_ptr<WrapperRoot<T>>
{
if (dynamic_cast<A*>(&root)) {
//root is an A, return a WrapperA
return make_unique<WrapperA<T>>();
}
else if (dynamic_cast<B*>(&root)) {
//root is a B, return a WrapperB
return make_unique<WrapperB<T>>();
}
throw std::runtime_error("No wrapper for that type");
}
模板
自动换行(根和根)->唯一\u ptr
{
如果(dynamic_cast)事实证明,这可以通过类型擦除、访问者模式和一点间接寻址来解决。解决方案是干净的,并且不需要我们在wrap
函数中重新实现动态调度
其核心思想是引入包装选择器
访客界面:
class WrapperSelector {
public:
virtual auto visit(A&) -> void = 0;
virtual auto visit(B&) -> void = 0;
};
需要稍微修改Root
层次结构以接受此访问者,并执行双重分派:
class Root {
public:
virtual ~Root() {}
virtual auto accept(WrapperSelector&) -> void = 0;
};
class A : public Root {
public:
virtual auto accept(WrapperSelector& wrapper) -> void {
wrapper.visit(*this);
}
};
class B : public Root {
public:
virtual auto accept(WrapperSelector& wrapper) -> void {
wrapper.visit(*this);
}
};
到目前为止,这是C++中的Bug标准访问者模式。我们现在介绍的是模板类<代码> WrasPopErrultIMPL:公共WrasPopeExcel < /COD>。由于它是模板化的,但只通过非模板化接口使用,这实现了类型擦除。在内部,我们构造了这个代码> WrasPopelErristIbl/代码>作为容器。借用的
WrapperRoot
指针,我们将所选包装写入其中。在accept
/visit
序列结束后,该指针将填充包装,因此虚拟方法不需要返回模板参数化类型。此外,accept
方法除了选择相应的visit
方法,因此Root
层次结构中的类型不需要知道WrapperRoot
层次结构–具体的WrapperSelector
将处理此映射
template<typename T>
class WrapperSelectorImpl : public WrapperSelector {
unique_ptr<WrapperRoot<T>>& _wrapper;
public:
explicit WrapperSelectorImpl(unique_ptr<WrapperRoot<T>>& wrapper)
: _wrapper(wrapper)
{}
virtual auto visit(A&) -> void override {
_wrapper = make_unique<WrapperA<T>>();
}
virtual auto visit(B&) -> void override {
_wrapper = make_unique<WrapperB<T>>();
}
};
一般化
上述技术可用于实现任意模板化的虚拟方法,或具有任意返回类型的访问者模式。其先决条件是对访问者模式的最低支持:
- 一些
Subject
类或类层次结构具有虚拟无效接受(SubjectVisitor&v){v.visit(*this);}
- 一些
SubjectVisitor
与Subject
类层次结构中每个类S
的虚拟无效访问(&)=0
方法交互
现在假设我们希望在Subject
层次结构中实现具有以下伪签名的方法:
class Subject {
...
template<typename R, typename T, typename... Args>
virtual R frobnicate(Args... args) = 0;
}
请注意,这假设返回值是默认可构造的。如果不是这种情况,则用unique\u ptr
替换ReturnType
可能是一种解决方案
我们现在必须提供一个提供实际实现的类ConcreteSubjectVisitor:publicSubjectVisitor
template<typename ReturnType, typename T>
class ConcreteSubjectVisitor : public SubjectVisitor {
ReturnType& return_value;
ArgType something;
public:
ConcreteSubjectVisitor(ReturnType& ret, ArgType& other_arg) : return_value(ret), something(other_arg) {}
virtual void visit(S1&) override { ... }
virtual void visit(S2&) override { ... }
...
};
模板
类ConcreteSubjectVisitor:公共主题Visitor{
返回类型和返回值;
输入某物;
公众:
ConcreteSubjectVisitor(ReturnType&ret,ArgType&other_-arg):返回值(ret),某物(other_-arg){}
虚拟无效访问(S1&)重写{…}
虚拟无效访问(S2&)覆盖{…}
...
};
唯一重要的是它可以写入返回值。请注意,访问者可以通过构造函数获取额外的参数,这使得它与std::bind
函数或构造lambda有些关联。然后visit
定义包含实际的代码,该代码可以访问所有类型访问者的米数,以及访问者的所有构造函数参数
未解决的问题:
- 处理不可默认构造的返回类型(指针或自定义默认值)
void
返回类型的专门化(问题退化为“正常”访客模式)
- 对完全多方法的泛化(如果方法是咖喱格式的,则不重要)
- 提供方便的界面
- 常量正确性(必须基于每个访问者应用,
Subject::accept
可以作为常量和非常量提供)
我不太理解WrapperRoot
和Root
之间的关系。在您的测试用例中,似乎没有。我特别感兴趣的是模板参数在该关系中扮演什么角色。是否有一个独立于的WrapperRoot
接口
template<typename T>
class WrapperSelectorImpl : public WrapperSelector {
unique_ptr<WrapperRoot<T>>& _wrapper;
public:
explicit WrapperSelectorImpl(unique_ptr<WrapperRoot<T>>& wrapper)
: _wrapper(wrapper)
{}
virtual auto visit(A&) -> void override {
_wrapper = make_unique<WrapperA<T>>();
}
virtual auto visit(B&) -> void override {
_wrapper = make_unique<WrapperB<T>>();
}
};
template<typename T>
auto wrap(Root& obj) -> unique_ptr<WrapperRoot<T>> {
unique_ptr<WrapperRoot<T>> wrapper;
WrapperSelectorImpl<T> wrapper_selector(wrapper);
obj.accept(wrapper_selector);
return wrapper;
}
class Subject {
...
template<typename R, typename T, typename... Args>
virtual R frobnicate(Args... args) = 0;
}
// most general implementation
template<typename ReturnType, typename Subject, typename Visitor, typename... Args>
auto manage_visitor(Subject& subject, Args... args) -> ReturnType {
ReturnType return_value;
Visitor visitor(return_value, std::forward(args)...);
subject.accept(visitor);
return return_value;
}
class Subject {
...
template<typename R, typename T, typename... Args>
R frobnicate(Args... args) {
return manage_visitor<R, Subject, ConcreteSubjectVisitor<R, T>>(*this, std::forward(args)...);
}
};
template<typename ReturnType, typename T>
class ConcreteSubjectVisitor : public SubjectVisitor {
ReturnType& return_value;
ArgType something;
public:
ConcreteSubjectVisitor(ReturnType& ret, ArgType& other_arg) : return_value(ret), something(other_arg) {}
virtual void visit(S1&) override { ... }
virtual void visit(S2&) override { ... }
...
};