【问题标题】:Maps containing themselves包含自身的地图
【发布时间】:2026-01-20 03:55:02
【问题描述】:

目前我正在努力处理应该包含自己的地图。但是编译的时候不知道嵌套的深度。

std::map<Key, std::map<Key, std::map<Key, std::map<Key, ...>>>>

有没有一种方法可以在不无限重复自己的情况下实现这个目标?

【问题讨论】:

  • 没有。如果它没有逻辑意义,你就不能在代码中表示它。
  • 不,你可能做不到。对于像这样的自引用结构,您可能需要一个包含指向映射的指针的映射。另请注意,要包含任何有意义的信息,可能需要像结构/类/联合这样的东西,因此它也可以包含其他东西(一些实际数据)。
  • 对,它们的“最后一个”映射应该包含一个值。谢谢,我会试着想点别的。
  • 您要解决的原始问题是什么?或许我们可以建议一个更合适的数据结构。
  • 您可以使用一些元编程技术通过递归模板来实际生成它——它是否有用还是合理的,仍然是一个悬而未决的问题。在 C++ 中,你可以做很多既没用也不理智的事情。

标签: c++ stl c++11 std


【解决方案1】:

自引用数据结构的金锤是指针的使用。在您的特定情况下,要实现一棵树,您可以这样做:

template <typename Key, typename Value>
struct Node {
   Value data;
   std::map< Key, std::shared_ptr<Node> > child;
// ...
};

树中的每个节点都包含一个值和一组通过共享指针映射维护的子Nodes。 std::map 要求(根据标准)存储的类型是完整的,但 shared_ptr 只需要在创建时类型是完整的,这允许这种数据结构。一个普通的Node* 也可以,但是你必须手动管理内存。

【讨论】:

    【解决方案2】:

    我认为您不能以完全类型安全的方式编写实际类型 C++,因为实例化模板时使用的所有类型都必须是 实例化模板时完成,基本需要什么 要做的事情是这样的:

    typedef boost::variant<Value, std::map<Key, ExtendedValue> > ExtendedValue;
    typedef std::map<Key, ExtendedValue> MyMap;
    

    这会导致模板依赖项中的递归:为了 实例化std::map,ExtendedValue必须是完整的,但是为了 要实例化ExtendedValuestd::map 必须是完整的。

    您应该能够使用以下方法创建类型安全性较低的版本:

    typedef std::map<Key, boost::any> MyMap;
    

    只要你在地图中输入的都是ValueMyMap,这个 应该管用。 (我在 Java 中做过类似的事情,其中​​映射的 类型是Object。)

    或者,您可以映射到一个 boost::variant ,其中包含一个 指向地图的指针,而不是地图本身。即使这也很困难 但是,除非您将地图包装在一个类中,否则要命名。您可以轻松 前向声明一个类,并使用指向它的指针,但你不能声明 模板的实例化,直到模板中使用的类型为 至少知道。所以你必须写这样的东西:

    class MyMap;
    typedef boost::variant<Value, MyMap*> ExtendedValue;
    class MyMap : private std::map<Key, ExtendedValue>
    {
    public:
        using ...;
    };
    

    由于您必须使用指针,这可能是无论如何都要走的路;你 然后可以包装您需要的成员函数以确保正确的成员 管理也是如此。 (在这种情况下,像 operator[] 这样的函数会 可能必须返回一个代理,以便您可以拦截写入,并且 进行分配。)

    【讨论】:

      【解决方案3】:

      xml 的简单实现:Node 扩展了自身的列表

        class Node :
           public std::unordered_map< std::string, std::list< Node > >
        {
        public:
      
           Node( const std::string & tag_ ) : tag( tag_ ){}
      
           void print( const std::string & indent ) const
           {
              std::cout << indent << '<' << tag;
              if( ! attributes.empty())
              {
                 for( std::unordered_map< std::string, std::string >::const_iterator it2 = attributes.begin(); it2 != attributes.end(); ++it2 )
                 {
                    std::cout << ' ' << it2->first << "=\"" << it2->second << "\" ";
                 }
              }
              std::cout << '>' << std::endl;
              if( ! text.empty())
              {
                 std::cout << indent << text << std::endl;
              }
              for( Node::const_iterator it1 = begin(); it1 != end(); ++it1 )
              {
                 const std::list< Node > & lst = it1->second;
                 for( std::list< Node >::const_iterator it2 = lst.begin(); it2 != lst.end(); ++it2 )
                 {
                    (*it2).print( indent + '\t' );
                 }
              }
              std::cout << indent << "</" << tag << '>' << std::endl;
           }
      
           std::string                                    tag;
           std::string                                    text;
           std::unordered_map< std::string, std::string > attributes;
        };
      
        int _tmain(int argc, _TCHAR* argv[])
        {
           Node title( "title" );
           title.text = "Title of the html page";
      
           Node head( "head" );
           head["title"].push_back( title );
      
           Node html( "html" );
           html["head"].push_back( head );
      
           Node body( "body" );
           body.attributes["bgcolor"] = "#F0F0F0";
           Node p1( "p" );
           p1.text = "Creativity and imagination are limitless.";
           body["p"].push_back( p1 );
           Node p2( "p" );
           p2.text = "Don't listen to the annoying guys who say your projects are dreams";
           body["p"].push_back( p2 );
           html["body"].push_back( body );
      
           html.print( "" );
      
           /* Result:
           <html>
                   <head>
                           <title>
                           Title of the html page
                           </title>
                   </head>
                   <body bgcolor="#F0F0F0" >
                           <p>
                           Creativity and imagination are limitless.
                           </p>
                           <p>
                           Don't listen to the annoying guys who say your projects are dreams
                           </p>
                   </body>
           </html>
           */
            return 0;
        }
      

      由于根本没有定义虚方法,所以不需要虚析构函数。

      【讨论】:

      • 未定义行为:当您使用 RecursiveMap 实例化 std::map 时,它并不完整。 (但我认为你甚至不会走那么远;模板本身是无限递归的,这意味着编译器将需要比你实例化它更多的内存。)
      • 乍一看,是的,但只是为了好玩,我已经编译它并使用 VisualC++ 2010 运行它:它可以工作!我不知道为什么...
      • 这主要取决于地图的实现,因为某些容器的某些实现不需要类型是完整的。例如,Boost.Containers 提供的实现可以对不完整的(在声明时)类型进行操作。
      • 我刚刚用 g++ 4.7.1 编译了它,并且......它有效!有人解释一下吗?
      • 继承自std::map真的?您应该避免从不是为扩展而设计的类型继承。
      【解决方案4】:

      Short of Algebraic Data Types,递归变体可以满足您的需求:

      using data_type = boost::make_recursive_variant<
          Value
          , std::map<Key, boost::recursive_variant_>
      >::type;
      

      Demo on LWS.

      请注意,这允许值的“形状”(换句话说,递归深度)直到运行时才知道。

      虽然还有其他方法可以使用 Boost.Variant 声明和使用递归类型,但我建议您 check out the documentation 找出最适合您的情况。

      (特别是通过使用std::map&lt;Value, data_type&gt; 作为*类型而不是data_type 本身,我禁止离开,即形状仅为Value 的值。但这只是为了方便使用std::initializer_list 的构造函数std::map.)

      【讨论】: