【问题标题】:Are C++ recursive type definitions possible, in particular can I put a vector<T> within the definition of T?C++ 递归类型定义是否可能,特别是我可以将 vector<T> 放在 T 的定义中吗?
【发布时间】:2011-09-24 22:11:26
【问题描述】:

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

struct Move
{
    int src;
    int dst;
};

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

我必须承认,我认为直接执行此操作是不可能的,我认为 MoveTree 中的 MoveTree 向量将被禁止。但无论如何我都试过了,效果很好。我正在使用 Microsoft Visual Studio 2010 Express。

这是便携的吗?这是好习惯吗?我有什么需要担心的吗?

编辑:我已经问了第二个question,希望能找到一个好的方法。

【问题讨论】:

  • std::vector& 变体; MoveTree(): 变体(*new std::vector){}; ~MoveTree(){delete &variation;} 似乎是可移植的
  • @Scheff 你确实知道这个问题是在 2011 年提出和回答的,对吧?
  • @Scheff 也许我读到的恶意比你想象的要多。事后跟进最新技术的事实更新是一回事,但嘲笑他们缺乏未来知识的帖子似乎不太光彩。
  • @SteamyThePunk “将被禁止”在我看来就像一个文字游戏(不小心混入了德语单词)。我不知道“verboten”是一个常见的英语单词。刚刚发现相反的情况,如牛津词典。列出它(用 (Surprise.) 表示德国血统)。刚刚学到了其他东西......(请忘记它。)

标签: c++


【解决方案1】:

std::vector 中的MoveTree 元素位于已分配的(如new [])数组中。只有控制信息(指向数组的指针、大小等)存储在 std::vector 内的 MoveTree 内。

【讨论】:

  • 也许我错了,但 stl 实现可能会选择在任何地方分配数组,例如,可以在向量对象本身保留的内存中分配短数组
【解决方案2】:

MoveTree 在其定义中是一个不完整的类型。该标准不保证实例化具有不完整类型的 STL 模板。

【讨论】:

  • 我认为这需要更新。请看this
【解决方案3】:

使用vector时,MoveMoveTree都应该定义拷贝构造函数和赋值运算符,否则会使用编译器生成的,这可能会导致问题。

【讨论】:

  • 我看不出是否提供用户定义的复制构造函数和赋值运算符的决定应该以任何方式受到对象是否在向量中使用的影响。事实上,我不明白为什么这些类中的任何一个都需要这些东西。
  • @Benjamin,感谢您抽出宝贵时间发表评论。我接受这与他关于不完整类型的问题无关的观点。 vector 需要复制构造函数;在他的情况下,默认一个。他问有什么需要注意的,我认为值得指出。
  • 当前的代码(即包含其自身类型的向量的类型)是不正确的,无论您是否提供构造函数、赋值运算符或都不提供它们。
【解决方案4】:

不,它不是便携式的。 codepad.org 不会编译它。

t.cpp:14:   instantiated from here
Line 215: error: '__gnu_cxx::_SGIAssignableConcept<_Tp>::__a' has incomplete type
compilation terminated due to -Wfatal-errors.

【讨论】:

  • @Eamon 4.2.2 不会,至少如果您使用严格验证选项时不会。 (我认为是-D_GLIBCXX_CONCEPT_CHECKS 控制它。)这是未定义的行为。 (原本打算 C++11 需要诊断,但这是概念包的一部分,我认为它不会进入最终版本。)
  • 好的,如果我打开该检查 - 它会失败。这仍然不是一个巨大的可移植性绊脚石。
【解决方案5】:

C++ 标准 (2003) 明确指出,用不完整类型实例化标准容器会调用未定义行为。

规范在 §17.4.3.6/2 中说,

特别是在以下情况下效果是不确定的:

__ [..]
— 如果在实例化模板组件时将不完整类型 (3.9) 用作模板参数。
__ [..]

随着 C++17 标准的改变,事情发生了变化,它明确地允许 std::liststd::vectorstd::forward_list 的这种类型的递归。如需参考,请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4510.html 和此答案:How can I declare a member vector of the same class?

【讨论】:

  • +1 表示实际给出正确答案,并引用了这样做的标准。
  • 我试图找到解释 where 是上述代码中实例化的向量的确切引用。有什么提示吗?
  • 来来回回,我很确信你引用的声明实际上是模板的实例化点。如果它在成员函数中(包括构造函数,无论是用户定义的还是隐式生成的),那么代码将是正确的,因为在方法的定义中一个类被认为是完整的(即,如果实例化点在 MoveTree::MoveTree() : variation() {}那么代码就是正确的(第 14.7.1 节解释了隐式实例的规则,供那些好奇阅读的人使用)
  • @Nawaz:不,我不确定实例化点在哪里,我只是添加了一条评论说明它为什么重要:如果模板被认为是实例化的(它不是,参考 I如果§14.7.1 另有说明)那么代码将是正确的,我坚持认为它不是。即:struct Test { std::vector&lt;Test&gt;* p; Test() : p ( new std::vector&lt;Test&gt;() ) {} ~Test() { delete p; } };,作为示例是有效的,因为实例化(在这种 不同 情况下)不是在类定义中执行,而是在构造函数中执行。
  • 我认为这需要更新。请参阅this
【解决方案6】:

使用指向 Vector 中类型的指针,这将是可移植的。

struct Move
    {
        int src;
        int dst;
    };

struct MoveTree;

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

【讨论】:

  • 如果你唯一的工具是锤子,那么一切都是钉子。
  • 或者,您可以使用shared_ptr&lt;vector&lt;MoveTree&gt;&gt; variation 并且仅动态分配向量(它将按值包含对象)。这具有改变对象的值语义的负面影响,因此您将需要提供复制构造函数/赋值运算符。
  • @David Allen Finch:“如果唯一的工具......”我理解这个成语,但不是你将它应用于什么。指向非指针的 STL、std::vectorstd:vector
  • @Merlyn Morgan-Graham:不。当标准容器被实例化时,包含的类型必须是complete。现在,如果vector 是动态分配的,则实例化点从成员的声明(在本例中为shared_ptr)移动到稍后实际分配vector 的时间。该分配将在构造期间发生,并且在处理成员函数时始终将类型视为完整。也就是说,在MoveTree::MoveTree() : variation( new std::vector&lt;MoveTree&gt;() ) {} 中,MoveTree 是完整的。阅读@Nawaz 的答案中的 cmets。
  • 请注意,动态分配的对象数量大致相同(即shared_ptr),因为每个MoveTree要么由smart_ptr持有,要么由smart_ptr持有一个向量,我更喜欢保存向量的方法,因为它允许您发布对其的引用,并且不需要修改用户代码(可以遍历vector&lt;MoveTree&gt;,不需要执行额外的取消引用)。
猜你喜欢
  • 2014-07-02
  • 2020-04-30
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多