jme*_*jme 6 c++ oop inheritance templates design-patterns
假设我正在Tree上课.我将通过Tree::Node类表示树的节点.类的方法可能返回Tree::Node对象并将它们作为参数,例如获取节点父节点的方法:Node getParent(Node).
我也想SpecialTree上课.SpecialTree应该扩展a的接口Tree并且可以在任何地方使用Tree.
在幕后,Tree并SpecialTree可能有完全不同的实现.例如,我可能会使用一个库的GraphA类来实现Tree,所以Tree::Node是一个瘦包装或为一个typedef GraphA::Node.另一方面,SpecialTree可以根据GraphB对象实现,并且Tree::Node包装a GraphB::Node.
我稍后会有处理树的函数,比如深度优先搜索函数.这个功能应该同时接受Tree和SpecialTree对象互换.
我将使用模板化接口类来定义树和特殊树的接口.template参数将是实现类.例如:
template <typename Implementation>
class TreeInterface
{
public:
typedef typename Implementation::Node Node;
virtual Node addNode() = 0;
virtual Node getParent(Node) = 0;
};
class TreeImplementation
{
GraphA graph;
public:
typedef GraphA::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent() { // ...return the parent... }
};
class Tree : public TreeInterface<TreeImplementation>
{
TreeImplementation* impl;
public:
Tree() : impl(new TreeImplementation);
~Tree() { delete impl; }
virtual Node addNode() { return impl->addNode(); }
virtual Node getParent() { return impl->getParent(); }
};
Run Code Online (Sandbox Code Playgroud)
我可以SpecialTreeInterface从以下来源TreeInterface:
template <typename Implementation>
class SpecialTreeInterface : public TreeInterface<Implementation>
{
virtual void specialTreeFunction() = 0;
};
Run Code Online (Sandbox Code Playgroud)
并定义SpecialTree和SpecialTreeImplementation类似Tree和TreeImplementation.
我的深度优先搜索功能可能如下所示:
template <typename T>
void depthFirstSearch(TreeInterface<T>& tree);
Run Code Online (Sandbox Code Playgroud)
并且由于SpecialTree派生自TreeInterface,这将适用于Tree对象和SpecialTree对象.
另一种方法是更多地依赖模板,因此SpecialTree根本不是TreeInterface类型层次结构的后代.在这种情况下,我的DFS功能将如下所示template <typename T> depthFirstSearch(T& tree).这也抛出了严格定义的接口,准确描述了a Tree或其后代应该具有的方法.因为a SpecialTree应该总是像a一样Tree,但是提供一些额外的方法,我喜欢使用接口.
我不是将TreeInterface模板参数作为实现,而是使用"表示"类来定义Node外观(它还必须定义Arc外观,等等).但由于我可能需要其中一个用于每个实现,我想我想将它与实现类本身保持在一起.
使用此模式可以获得什么?大多数情况下,耦合更松散.如果我想改变后面的实现Tree,SpecialTree根本不介意,因为它只继承了接口.
那么,这个模式有名字吗?我通过存储指针使用手柄本体模式ContourTreeImplementation在ContourTree.但是如何使用模板化界面呢?这有名字吗?
有一个更好的方法吗?看起来我似乎在重复自己,写了很多样板代码,但那些嵌套的Node类给了我麻烦.如果Tree::Node和SpecialTree::Node有相当类似的实现,我可以定义NodeInterface一个接口Node中TreeInterface,并覆盖节点类在执行Tree和SpecialTree.但实际上,我不能保证这是真的.Tree::Node可以包装一个GraphA::Node,并SpecialTree::Node可以包装一个整数.所以这种方法不太合适,但似乎仍有改进的余地.有什么想法吗?
看起来像是Curiously Recurring Template Pattern和Pimpl idiom的混合体。
\n\n在 CRTP 中,我们Tree得出TreeInterface<Tree>:在您的代码中,您派生Tree自TreeInterface<TreeImplementation>. 所以这也正如@ElliottFrisch所说:它是策略模式的应用。代码的某些部分关心是否Tree符合,而某些其他部分则关心它使用特定策略的TreeInterface事实。 TreeImplementation
\n\n\n有一个更好的方法吗?看来我确实重复了很多
\n
好吧,这取决于您的运行时要求。当我查看你的代码时,让我惊讶的是你正在使用virtual方法 \xe2\x80\x94 slooooow!你的类层次结构如下所示:
Tree is a child of\n TreeInterface<TreeImplementation>\n\nSpecialTree is a child of\n TreeInterface<SpecialTreeImplementation>\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,事实恰好TreeInterface<X>::addNode()是virtual与是否虚拟完全没有关系! TreeInterface<Y>::addNode()因此,创建这些方法virtual并不会为我们带来任何运行时多态性;我无法编写一个接受 任意实例的函数,TreeInterfaceBase因为我们没有一个TreeInterfaceBase. 我们所拥有的只是一包不相关的基类TreeInterface<T>。
那么,为什么会有这些virtual方法存在呢?啊哈。您用于virtual将信息从派生类传递回父级:子级可以通过继承“查看”其父级,父级可以通过virtual. 这是通常通过 CRTP 解决的问题。
因此,如果我们使用 CRTP(因此不再需要这些virtual东西),我们就会得到这样的结果:
template <typename Parent>\nstruct TreeInterface {\n using Node = typename Parent::Node;\n Node addNode() { return static_cast<Parent*>(this)->addNode(); }\n Node getParent(Node n) const { return static_cast<Parent*>(this)->getParent(n); }\n};\n\nstruct ATree : public TreeInterface<ATree> {\n GraphA graph;\n typedef GraphA::Node Node;\n\n Node addNode() { return graph.addNode(); }\n Node getParent(Node n) const { // ...return the parent... }\n};\n\nstruct BTree : public TreeInterface<BTree> {\n GraphB graph;\n typedef GraphB::Node Node;\n\n Node addNode() { return graph.addNode(); }\n Node getParent(Node n) const { // ...return the parent... }\n};\n\ntemplate <typename Implementation>\nvoid depthFirstSearch(TreeInterface<Implementation>& tree);\nRun Code Online (Sandbox Code Playgroud)\n\n此时有人可能会说我们根本不需要丑陋的指针转换 CRTP,我们可以只写
\n\nstruct ATree {\n GraphA graph;\n typedef GraphA::Node Node;\n\n Node addNode() { return graph.addNode(); }\n Node getParent(Node n) const { // ...return the parent... }\n};\n\nstruct BTree {\n GraphB graph;\n typedef GraphB::Node Node;\n\n Node addNode() { return graph.addNode(); }\n Node getParent(Node n) const { // ...return the parent... }\n};\n\ntemplate <typename Tree>\nvoid depthFirstSearch(Tree& tree);\nRun Code Online (Sandbox Code Playgroud)\n\n我个人同意他们的观点。
\n\n好吧,您担心的是,无法通过类型系统确保调用T者传递给的类型depthFirstSearch实际上符合TreeInterface. 嗯,我认为执行该限制的最 C++11 风格的方法是使用static_assert. 例如:
template<typename Tree>\nconstexpr bool conforms_to_TreeInterface() {\n using Node = typename Tree::Node; // we\'d better have a Node typedef\n static_assert(std::is_same<decltype(std::declval<Tree>().addNode()), Node>::value, "addNode() has the wrong type");\n static_assert(std::is_same<decltype(std::declval<Tree>().getParent(std::declval<Node>())), Node>::value, "getParent() has the wrong type");\n return true;\n}\n\ntemplate <typename T>\nvoid depthFirstSearch(T& tree)\n{\n static_assert(conforms_to_TreeInterface<T>(), "T must conform to our defined TreeInterface");\n ...\n}\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,如果不符合,我的conforms_to_TreeInterface<T>()实际上会 static-assert-fail ;T它永远不会真正回来false。您同样可以让它返回true或false然后点击static_assertin depthFirstSearch()。
无论如何,这就是我解决问题的方法。请注意,我的整篇文章的动机是摆脱那些低效且令人困惑的virtuals \xe2\x80\x94 其他人可能会抓住问题的不同方面并给出完全不同的答案。