C++:这个模式是否有名称,是否可以改进?

jme*_*jme 6 c++ oop inheritance templates design-patterns

动机

假设我正在Tree上课.我将通过Tree::Node类表示树的节点.类的方法可能返回Tree::Node对象并将它们作为参数,例如获取节点父节点的方法:Node getParent(Node).

我也想SpecialTree上课.SpecialTree应该扩展a的接口Tree并且可以在任何地方使用Tree.

在幕后,TreeSpecialTree可能有完全不同的实现.例如,我可能会使用一个库的GraphA类来实现Tree,所以Tree::Node是一个瘦包装或为一个typedef GraphA::Node.另一方面,SpecialTree可以根据GraphB对象实现,并且Tree::Node包装a GraphB::Node.

我稍后会有处理树的函数,比如深度优先搜索函数.这个功能应该同时接受TreeSpecialTree对象互换.

模式

我将使用模板化接口类来定义树和特殊树的接口.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)

并定义SpecialTreeSpecialTreeImplementation类似TreeTreeImplementation.

我的深度优先搜索功能可能如下所示:

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根本不介意,因为它只继承了接口.

问题

那么,这个模式有名字吗?我通过存储指针使用手柄本体模式ContourTreeImplementationContourTree.但是如何使用模板化界面呢?这有名字吗?

有一个更好的方法吗?看起来我似乎在重复自己,写了很多样板代码,但那些嵌套的Node类给了我麻烦.如果Tree::NodeSpecialTree::Node有相当类似的实现,我可以定义NodeInterface一个接口NodeTreeInterface,并覆盖节点类在执行TreeSpecialTree.但实际上,我不能保证这是真的.Tree::Node可以包装一个GraphA::Node,并SpecialTree::Node可以包装一个整数.所以这种方法不太合适,但似乎仍有改进的余地.有什么想法吗?

Quu*_*one 2

看起来像是Curiously Recurring Template PatternPimpl idiom的混合体。

\n\n

在 CRTP 中,我们Tree得出TreeInterface<Tree>:在您的代码中,您派生TreeTreeInterface<TreeImplementation>. 所以这也正如@ElliottFrisch所说:它是策略模式的应用。代码的某些部分关心是否Tree符合,而某些其他部分则关心它使用特定策略的TreeInterface事实。 TreeImplementation

\n\n
\n

有一个更好的方法吗?看来我确实重复了很多

\n
\n\n

好吧,这取决于您的运行时要求。当我查看你的代码时,让我惊讶的是你正在使用virtual方法 \xe2\x80\x94 slooooow!你的类层次结构如下所示:

\n\n
Tree is a child of\n  TreeInterface<TreeImplementation>\n\nSpecialTree is a child of\n  TreeInterface<SpecialTreeImplementation>\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,事实恰好TreeInterface<X>::addNode()virtual与是否虚拟完全没有关系! TreeInterface<Y>::addNode()因此,创建这些方法virtual并不会为我们带来任何运行时多态性;我无法编写一个接受 任意实例的函数,TreeInterfaceBase因为我们没有一个TreeInterfaceBase. 我们所拥有的只是一包不相关的基类TreeInterface<T>

\n\n

那么,为什么会有这些virtual方法存在呢?啊哈。您用于virtual将信息从派生类传递回父级:子级可以通过继承“查看”其父级,父级可以通过virtual. 这是通常通过 CRTP 解决的问题。

\n\n

因此,如果我们使用 CRTP(因此不再需要这些virtual东西),我们就会得到这样的结果:

\n\n
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);\n
Run Code Online (Sandbox Code Playgroud)\n\n

此时有人可能会说我们根本不需要丑陋的指针转换 CRTP,我们可以只写

\n\n
struct 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);\n
Run Code Online (Sandbox Code Playgroud)\n\n

我个人同意他们的观点。

\n\n

好吧,您担心的是,无法通过类型系统确保调用T者传递给的类型depthFirstSearch实际上符合TreeInterface. 嗯,我认为执行该限制的最 C++11 风格的方法是使用static_assert. 例如:

\n\n
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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,如果不符合,我的conforms_to_TreeInterface<T>()实际上会 static-assert-fail ;T它永远不会真正回来false。您同样可以让它返回truefalse然后点击static_assertin depthFirstSearch()

\n\n

无论如何,这就是我解决问题的方法。请注意,我的整篇文章的动机是摆脱那些低效且令人困惑的virtuals \xe2\x80\x94 其他人可能会抓住问题的不同方面并给出完全不同的答案。

\n