如何在C ++中模拟递归类型定义?

时间:2011-06-29 22:16:39

标签: c++ design-patterns

昨天我问过以下question,为方便起见,转载于此处;

“对于我的一个项目,我真正想做的就是这样做(将其简化为最低限度);

struct Move
{
    int src;
    int dst;
};

struct MoveTree
{
    Move move;
    std::vector<MoveTree> variation;
};

我必须承认,我认为不可能直接这样做,我认为MoveTree中的MoveTree向量将被禁止。但无论如何我都试过了,它的效果非常好。我正在使用Microsoft Visual Studio 2010 Express。

这可移植吗?这是好习惯吗?我有什么值得担心的吗?“

基本上社区的回答是否定的,我不能这样做,标准禁止它,所以它的工作意味着我很幸运。

所以我的新问题是。如何在合法的C ++中实现我想要的简单功能,而不会增加一堆令人讨厌的复杂性和痛苦?

5 个答案:

答案 0 :(得分:6)

您需要使用指针和动态分配。你应该使用智能指针,以确保你不泄漏任何东西。 boost::shared_ptr允许类型不完整,因此这是合法的:

std::vector< boost::shared_ptr<MoveTree> > variation;

(我不知道关于0x std::shared_ptr TBH,但它应该是相同的。)

答案 1 :(得分:3)

您可以使用Boost Pointer Container Library。它类似于使用std :: vector,但容器拥有指针,因此它会破坏对象。

您可以声明:

#include <boost/ptr_container/ptr_vector.hpp>
struct MoveTree{
  Move move;
  boost::ptr_vector<MoveTree> variation;
};

现在,如果您想添加新元素,可以使用:

variation.push_back(new MoveTree());

现在,容器拥有指针,您可以通过引用获取它,例如:

variation[i].move.src = ...

当容器被销毁时,对象将被销毁。

答案 2 :(得分:1)

我手边没有C ++标准的副本,所以我无法检查标准,但问题出在这里:

  • 类不能包含自己的具体实例,即使是过渡性的 - 例如,struct foo { foo x; }是非法的,原因很明显。更一般地说,除了成员函数体之外,您不能在其任何成员中引用结构的大小。
  • 类可以包含指向自己的指针 - struct foo { foo *x; }完全没问题

所以问题是,std::vector是否定义了直接或间接依赖sizeof(MoveTree)的任何成员(成员函数除外)?

显然,std::vector不能拥有MoveTree本身的静态成员,因为这意味着空向量会调用MoveTree构造函数。然而,人们可以设想std :: vector与单元素向量的对齐char inlineStorage[sizeof(MoveTree)]优化。撇开这是否会提高性能,目前的问题是标准是否允许实施采取这种方法。

也就是说,出于不同的原因,它仍然是一个坏主意:因为向量在调整存储大小时必须复制它们的元素,所以在向量中使用带有昂贵复制构造函数的元素是个坏主意。这里我们有一个类,其复制构造函数必须以递归方式重新创建整个树。最好使用智能指针类来间接引用子节点以避免这种开销:

std::vector<boost::shared_ptr<MoveTree> > variation;
// in C++0x:
std::vector<std::shared_ptr<MoveTree> > variation;
// in C++0x you can also use for lower overhead:
std::vector<std::unique_ptr<MoveTree> > variation;
// you must then use this pattern to push:

variation.push_back(std::move(std::unique_ptr<MoveTree>(new MoveTree())));

答案 3 :(得分:1)

如果您可以在任何模板中使用不完整类型MoveTree作为模板参数,那么请使用其他答案中的一个解决方案(例如Cat Plus Plus),或者只使用你的原始解决方案,添加一些沉重的评论,然后再做你的忏悔。

如果您在任何模板参数仍然不完整时无法使用它,则可以使用pimpl idiom解决此问题。

到定义实现类时,MoveTree类将完成:

struct Move
{
    int src;
    int dst;
};

struct MoveTreeImpl;

struct MoveTree
{
    Move move;

    // Todo: Implement RAII here for the impl
    // Todo: Provide impl accessor functions here

private:
    MoveTreeImpl* impl;
};

struct MoveTreeImpl
{
    std::vector<MoveTree> variation;
};

这个解决方案有毛茸茸的部分:

由于您试图避免直接实例化任何类型不完整的模板,因此您必须手动实现RAII。您无法获得std::scoped_ptr<MoveTreeImpl>的帮助,因为MoveTreeImpl也不完整。

您的访问者功能有哪些签名?你可以从访问者那里返回std::vector<MoveTree>&吗?我不确定 - 我们试图避免直接使用MoveTree的模板。这可能有所不同,因为它不是数据成员,但我不确定:)

修改

阅读更多内容,并获得其他用户的回复,似乎很多这些废话是不必要的。只有标准容器才有限制。您可以使用pimpl实现std:scoped_ptr<MoveTreeImpl>,我认为从访问者函数返回std::vector<MoveTree>&,两者都没有问题。

答案 4 :(得分:0)

使用指针(或智能指针)指向Vector中的类型,这将是可移植的。这是因为只需声明您不需要定义就可以完成指向类型的指针。

struct Move
    {
        int src;
        int dst;
    };

struct MoveTree;

struct MoveTree
    {
        Move move;
        std::vector<MoveTree*> variation;
    };

如果您需要管理类型,请使用可以为您处理删除的智能指针。

原始答案to this question