C++ 假设sizeof(std::unordered_map<;std::string,T>;)对于所有T都是相同的,实际上是安全的?

C++ 假设sizeof(std::unordered_map<;std::string,T>;)对于所有T都是相同的,实际上是安全的?,c++,c++11,stl,language-lawyer,C++,C++11,Stl,Language Lawyer,我所处的情况是,在两个类的定义之间有一个循环依赖循环,在这种情况下(据我所知),两个类都需要另一个类型为完整类型,以便正确定义它们 简而言之,我需要的是正在发生的事情的简化版本: struct Map; struct Node { // some interface... private: // this cannot be done because Map is an incomplete type char buffer[sizeof(Map)]; // p

我所处的情况是,在两个类的定义之间有一个循环依赖循环,在这种情况下(据我所知),两个类都需要另一个类型为完整类型,以便正确定义它们

简而言之,我需要的是正在发生的事情的简化版本:

struct Map;

struct Node {
    // some interface...
private:
    // this cannot be done because Map is an incomplete type
    char buffer[sizeof(Map)];
    // plus other stuff...
    void* dummy;
};

struct Map {
    // some interface...
private:
    // this is Map's only member
    std::unordered_map<std::string, Node> map_;
};
这基本上依赖于一个假设,即所有T的
std::unordered_map
大小相同,这在我使用GCC的测试中似乎是正确的

因此,我的问题有三个:

    《C++标准》中有什么要求这个假设成立吗?(我假设没有,但如果有,我会惊喜不已……)

  • 如果不是,那么假设它适用于所有合理的实现,并且我的修订版本中的静态断言永远不会触发,这实际上安全吗

  • 最后,对于这个问题,有没有更好的解决方法,我还没有想到?我相信有可能我可以做一些我没有想到的事情,但不幸的是我想不出任何事情

    • 1)否

      2) 我不确定

      3) 您也可以使用设计模式工厂方法。您的工厂将基于
      Map
      variant返回对象(编辑:我的意思是您将使用Map variant实例作为参数,工厂方法实现将使用该信息相应地创建返回对象),并且它可以预先分配缓冲区到正确的大小。

      1)否

      2) STL容器不能用不完整的类型实例化。然而,显然有些编译器允许这样做。不允许这样做不是一个微不足道的决定,在许多情况下,你的假设确实是正确的。这篇文章可能会引起你的兴趣。根据标准,如果不添加一层间接层,这个问题是无法解决的,您不想这样做。我只是要提醒一下,你确实没有按照标准做事

      话虽如此,我认为您的解决方案是使用stl容器的最佳解决方案。当大小确实超过预期大小时,静态断言确实会发出警告

      3) 是的,通过添加另一层间接寻址,我的解决方案如下:

      问题是,对象的大小取决于其数组的大小。假设您有一个对象A和对象B:

      struct A
      {
         char sizeof[B]
      }
      
      struct B
      {
          char sizeof[A]
      }
      
      对象A将增长,以容纳B大小的字符。但反过来,对象B将不得不增长。我想你能看到这是怎么回事。我知道这正是你的问题,但我认为基本原则非常相似

      在这种特殊情况下,我将通过改变

      char buffer[sizeof(Map)];
      
      行仅作为指针:

      char* buffer
      
      并在初始化后动态分配内存。Sow您的cpp文件将如下所示:

      //node.cpp
      //untested code
      node::node()
      {
          buffer = malloc(sizeof(map));
      }
      
      node::~node()
      {
          free buffer;
      }
      
      1) 可能不会

      2) 因为它看起来完全依赖于实现,所以这是一个很大的风险,因为这可能会在任何编译器更新时中断


      3) 您可以使用它来接受不完整的类型,从而解决您的问题。

      继续并假设。然后在构建时断言你是对的


      还有一些更奇特的解决方案,比如弄清楚boost递归数据结构是如何工作的,并在这里应用该技术(这可能需要编写自己的映射),或者只使用支持不完整数据结构的
      boost::
      容器。

      你能用3澄清你的建议吗?特别是,我不希望通过指针进行比修订版中更多的间接寻址(当
      static\u assert
      通过时,它就可以工作)
      Node
      实际上是一个包含多种类型中的一种而无需间接寻址的联合,可能的类型列表包括
      Map
      std::string
      bool
      。您的
      Node
      实例中有一个会在生命周期内返回一种类型,还是会有所不同?你熟悉设计模式吗?我一次只有一种类型,但它可以改变。如果类型已更改,则
      节点将显式调用当前内容上的右类型析构函数,并放置新类型的新对象,在过程中更新类型标记。我熟悉设计模式,但不明白您的意思——特别是,您似乎建议工厂返回一些通用指针类型,如
      void*
      ,以避免
      节点
      ,具体取决于
      Map
      的大小;这正是我不想要的,因为这是一个递归结构,额外的间接操作会很快累积起来。好吧,因为我不认为使用大小可变的char缓冲区来代替引用
      Map
      实例并从该实例获取数据的目的,我只能建议Decorator更改
      节点
      。因为您的建议添加了一个额外的间接层,这是我绝对不想要的。char缓冲区之所以存在,是因为我希望
      节点
      直接在其内部存储对象:
      节点
      可能包含
      std::string
      bool
      ,或者其他类型中的一种:其思想是有一个适当对齐的缓冲区,其大小足以容纳最大的对象,实际上可能最终成为
      Map
      。您的建议使得递归遍历此结构的每一步都需要更多的间接性。不,
      Map
      包含
      std::unordered_Map
      ,而不是直接
      节点
      ,并且前者实际上不会随着
      节点的大小而增长,因此这种情况与您所说的情况不同。实际上,
      std::unordered_map
      实际上不会随着
      节点的变化而改变大小,因为它不直接包含
      节点
      ;根据
      value\u类型
      pa大小确实发生变化的任何实现
      //node.cpp
      //untested code
      node::node()
      {
          buffer = malloc(sizeof(map));
      }
      
      node::~node()
      {
          free buffer;
      }