C++ 将运行时类型映射到并行类层次结构…;使用模板

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

我有两个具有1:1关系的类层次结构:一些普通类
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 { ... }
  ...
};