【问题标题】:C++ Struct that contains a map of itself包含自身映射的 C++ 结构
【发布时间】:2012-07-11 18:24:00
【问题描述】:

简单的问题:我如何让它工作?

struct A {
    double whatever; 
    std::unordered_map<std::string, A> mapToMoreA; 
}

g++ 错误:std::pair<_t1 _t2>::second 类型不完整

据我了解,在实例化映射时,编译器需要知道 A 的大小,但它不知道这一点,因为映射是在 A 的声明中声明的,所以解决这个问题的唯一方法是使用指向 A 的指针(不想这样做)?

【问题讨论】:

  • 对象需要具有兄弟而不是指针的容器并不常见。用例是什么?
  • @Mark Ransom:恰恰相反,通常需要一个带有兄弟姐妹的容器(例如,树实现)。使用指针通常只是一种解决方法。
  • @AndreyT:图表是最一般的情况,你必须有指针。递归对象 - 例如BinaryTree ::==(可选左:BinaryTree,可选右:BinaryTree)在概念上是常见的数据结构。但是要在 C/C++ 世界中实现它们,您需要指针。左右指针可能是隐藏的,外界看不到,但它们就在那里。即递归和分层数据结构通常就足够了,但不是通用的。
  • 你说在std contains中使用指针类似于struct TreeNode { TreeNode *left, *right; }?不,这是错误的和具有误导性的类比。例如,如果你实现了一个TreeNode 和std::vector 的指针 到里面的孩子,那将类似于struct TreeNode { TreeNode *(*childen)[2]; }(注意额外的*)。这类似于在TreeNode 中存储指向指针数组的指针。这是两层间接层,而不是一层。两个级别是完全没有必要的。

标签: c++ syntax compiler-errors


【解决方案1】:

大多数情况下,这将取决于容器实现细节(更准确地说,取决于在容器声明时实例化的内容和不实例化的内容)。显然,std::unordered_map 的实现要求类型是完整的。同时 GCC 的 std::map 实现在不完整类型的情况下编译得非常好。

为了说明这种差异的来源,请考虑以下示例。假设我们决定自己实现类似std::vector 的功能,并将我们的向量类声明如下

template <typename T> class my_vector {
  T *begin;
  T *end;
  ...
};

只要我们的类定义只包含指向T 的指针,类型T 就不需要对类定义本身来说是完整的。我们可以将my_vector 本身实例化为一个不完整的T 而不会出现任何问题

class X;
my_vector<X> v; // OK

当我们开始使用(并因此实例化)my_vector 的各个方法时,稍后将需要类型的“完整性”。

但是,如果出于某种原因我们决定将 T 的直接实例包含到我们的向量类中,事情就会发生变化

template <typename T>
class my_vector {
  T *begin;
  T *end;
  T dummy_element;
  ...
};

现在很早就需要T 的完整性,在my_vector 本身的实例化点

class X;
my_vector<X> v; // ERROR, incomplete type

在你的情况下一定发生了类似的事情。您正在处理的unordered_map 的定义不知何故包含A 的直接实例。这就是无法实例化的原因(显然,在这种情况下,您最终会得到无限递归类型)。

通过实现unordered_map 更好地考虑将确保不将A 作为直接成员包含在自身中。这样的实现不需要A 就可以完成。正如您自己所指出的,Boost 的 unordered_map 实现在这方面设计得更好。

【讨论】:

  • 只是从其他答案中添加: boost::unordered_map 似乎也允许不完整的类型。
【解决方案2】:

我不知道除了智能指针之外的任何 STL 容器都可以处理不完整的类型。但是,如果您不想使用指针,则可以使用包装器结构:

struct A {
    struct B { double whatever; }; 
    std::unordered_map<std::string, B> mapToB; 
};

编辑:如果以上不满足您的用例,这里是一个指针替代方案。

struct A {
    double whatever;
    std::unordered_map<std::string, std::unique_ptr<A>> mapToMoreA; 
};

您也可以只使用boost::unordered_map,它不仅支持不完整的类型,而且在 Visual Studio 中具有更高的调试性能,因为 Microsoft 的 std::unordered_map 实现由于过多的迭代器调试检查而效率极低。我不知道这两个容器的 gcc 有任何性能问题。

【讨论】:

  • 但是B不包含map成员。
  • 这是真的,接受不完整类型或使用指针的容器是另一种选择。
  • @AJG85:您可以通过模板轻松创建一个接受不完整类型的容器,例如 template class AcceptsIncomplete { private: BaseContainer* bptr; AcceptsIncomplete() : bptr( new BaseContainer ) {} ... 枚举 BaseContainer 的所有方法,通过 bptr 委托(这很痛苦。我希望有一种方法可以自动进行基于非继承的委托。
  • @AJG85: 或指向元素的指针:template class AcceptsIncomplete : public std::map > { ... // 不幸的是,继承自像 std 这样的 stl 类型::list 和 std::map 不鼓励
  • @KrazyGlew 大多数 STL 容器使用会导致不完整类型成为未定义行为的操作。但是,您可以创建适用于不完整类型的模板。不鼓励从 STL 容器继承,因为实现没有虚拟析构函数 AFAIK。
【解决方案3】:

Boost.Variant 有一个专门用于此目的的方便实用程序 - boost::recusive_wrapper&lt;&gt;。以下应该有效:

struct A {
    double whatever; 
    std::unordered_map<std::string, boost::recursive_wrapper<A>> mapToMoreA; 
};

唯一值得注意的缺点是 Boost.Variant 尚未更新以支持 C++11 移动语义。更新:在 Boost 1.56 中添加。

【讨论】:

    【解决方案4】:

    如果让地图保持指针是不可接受的,也许这对你有用:

    struct A {
      struct hidden;
      std::unique_ptr<hidden> pimpl;
    };
    
    struct A::hidden {
      double whatever;
      std::unordered_map<std::string, A> mapToMoreA;
    };
    

    【讨论】:

      【解决方案5】:

      在 C++ 中,对于不完整的类型,通常使用具有预定义常量大小的指针:

      这当然会改变您使用地图的方式:您必须取消引用 * 或 -&gt; 运算符才能访问成员,并且必须在某些时候使用 delete 指针。

      struct A
      {
          double bla;
          std::map<std::string, A*> mapToMoreA;
      };
      

      A 的成员函数应该在 struct 块内拆分成一个原型并稍后实现,否则 A 及其成员尚未完全定义:

      struct A
      {
          double bla;
          std::map<std::string, A*> mapToMoreA;
          void doStuff(const std::string& str);
      };
      
      void A::doStuff(const std::string& str)
      {
          mapToMoreA[str] = new A();
      }
      

      【讨论】:

        【解决方案6】:

        或者使用指向地图的指针。在这种情况下,指针必须是 void* 类型(可以隐藏在一组函数后面)。也许有 std::unordered_map 的替代品可以处理不完整的值类型。

        【讨论】:

          【解决方案7】:

          我认为您可以在定义 struct A; 之前转发它,编译器应该很高兴。

          编辑:所以在多次被否决后,我写了以下内容来看看我错过了什么:

          #include <boost/unordered_map.hpp>                                                                                      
          #include <string>
          #include <iostream>
          
          struct A;
          
          struct A {
              double whatever;
              boost::unordered_map<std::string, A> mapToMoreA;
          };
          
          int main(void)
          {
              A b;
              b.whatever = 2.5;
              b.mapToMoreA["abc"] = b;
              std::cerr << b.mapToMoreA["abc"].whatever << std::endl;
              return 0;
          }
          

          这在我的 mac 上使用 g++ 4.2.1 可以很好地编译,并在运行时打印出“2.5”(如预期的那样)。

          对不起,我没有 unordered_map 没有提升。是这个问题吗? (即,std::unordered_map 是否以某种方式对编译器施加了比 boost 更多的约束?)否则,我不确定我在这里遗漏了什么。那些对此表示反对的人,请用cmets启发我。谢谢!

          【讨论】:

          • 这不起作用,因为它仍然没有提供关于 A 大小的编译器信息,只是 A 存在。
          • 嗯,它对我有用。 (我刚刚编写了上面在 g++ 中编译良好的代码......将编辑。)
          • @Turix - 尝试删除您的前向声明。我打赌它会仍然编译。如果是这样,您的解决方案并没有真正改变任何东西。
          • @Rob 你是对的。 (尽管 OP 显然收到此代码的编译器错误。)但是,更大的一点是它不仅可以编译,而且还可以运行。编译器不知何故想出了如何处理其他人发布的“不完整类型”问题。
          • 查看 AnderyT 的答案 - 这完全取决于 unordered_map 的实现。仅仅因为它对你有用并不意味着标准要求它才能工作。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-13
          • 1970-01-01
          • 2021-01-19
          • 1970-01-01
          相关资源
          最近更新 更多