【问题标题】:Clear initialization for nested associative container with unknown recursion levels清除具有未知递归级别的嵌套关联容器的初始化
【发布时间】:2017-02-25 01:46:26
【问题描述】:

基本上我想要一些容器,类似于std::unordered_map<std::string, std::variant<unsigned, /*self_type*/>>。在这个容器中,无符号值是一个终端节点,而self_type代表一个子树,需要进一步搜索直到终端节点。

然而,这可以通过一个额外的包装类来实现。

struct node {
    std::unordered_map<std::string, 
                       std::variant<unsigned, std::unique_ptr<node>>> children;
};

很公平,但我想将其初始化为普通的 std::unordered_map 并带有嵌套的初始化器列表。例如:

{
    {
        "str1",
        {
            {"strstr1", 1},
            {"strstr2", 2}
        }
    },
    {"str2", 3}
}

也欢迎提出更合适的数据结构的建议。

【问题讨论】:

  • 你确定你需要一棵树吗?例如,您可以为所有数据使用一棵树,并为每个节点添加一些额外信息,以指示它是否是两个“独立”树之间的“分区”。
  • @JohnZwinck 我真的不需要树的树。树之树在纯树结构中是自然的,对吗?如果我使用纯基于节点的树结构,那么它会更容易做到。我选择 unordered_map 主要是为了性能问题,以展平树。然而,您的建议提醒我,即使这样做实际上也可能不会产生更好的性能。
  • 额外的{}s怎么样?
  • @Yakk 好的,在哪里添加?

标签: c++ c++17


【解决方案1】:

解决方案 1 - 使用包装类:

struct node {
    using myvar = boost::variant< unsigned, boost::recursive_wrapper< node > >;
    using childmap = std::unordered_map< std::string, myvar >;

    node() {}

    node( std::initializer_list< childmap::value_type > il ) :
        children( il ) {}

    childmap children;
};

我在这里使用 boost::variant,因为我没有可用的 std::variantboost::recursive_wrapper 是必需的,因为 boost::variant 通常需要完整的类型,但此时 node 仍然不完整。

boost::recursive_wrapper 没什么神奇的。它只是一个指针的包装器!众所周知,可以为不完整类型声明指针而不会出现问题。这个包装类只是通过处理分配、释放和提供值语义来隐藏使用指针的事实。它有 boost::variant 的特殊支持,使包装器完全透明,因此可以像根本没有包装器类一样使用该变体。

用法:

node n {
    { "foo", 1 },
    { "bar", 2 },
    { "baz", node {
        { "bim", 3 },
        { "bam", 4 }}
    }
};

n.children[ "fum" ] = 5;
n.children[ "fup" ] = node{{ "fap", 6 }};

初始化器列表中的显式“节点”是必需的,因为变体构造器无法从嵌套的初始化器列表中推断出类型。

演示:http://coliru.stacked-crooked.com/a/123c59a3523c39ed

解决方案 2 - 源自 unordered_map:

这消除了对“children”成员的需要。

struct nodemap :
    std::unordered_map< 
        std::string, 
        boost::variant< unsigned, boost::recursive_wrapper< nodemap > > >
{
    using base = std::unordered_map< 
        std::string, 
        boost::variant< unsigned, boost::recursive_wrapper< nodemap > > >;

    // Forward all constructors of the base class.
    using base::base;
};

用法:

nodemap n{
    { "foo", 1 },
    { "bar", 2 },
    { "baz", nodemap{
        { "bim", 3 },
        { "bam", 4 }}
    }};

n[ "fum" ] = 5;
n[ "fup" ] = nodemap{{ "fap", 6 }};

更多使用示例:

// Add something to a child nodemap. 
boost::get<nodemap>( n[ "baz" ] )[ "fap" ] = 7;

// This will throw a boost::bad_get exception because n[ "foo" ] is not a nodemap.
//boost::get<nodemap>( n[ "foo" ] )[ "fap" ] = 8;

// To avoid this problem, we can check if the child actually is a nodemap:
if( nodemap* pn = boost::get<nodemap>( &n[ "foo" ] ) )
{
    (*pn)[ "fap" ] = 8; 
}

演示:http://coliru.stacked-crooked.com/a/69914ec5646129f2

【讨论】:

  • 为什么map不需要完整类型?...这意味着,如果使用unordered_map,那么痛苦的node::create必须显式使用?
  • 正如stackoverflow.com/a/8487164/7571258 所写的那样,std::map 似乎使用了不同的存储模型,它不必复制元素。如果您在析构函数中删除子节点元素,则可以将 node::create 调用替换为 new node。为了防止复制节点的重复删除,您应该提供自定义复制构造函数和赋值运算符,或者将它们标记为“= delete”,这样它们就不能使用了。
  • 这仍然困扰了我几个小时......直到我终于找到了 unordered_map 的解决方案。结论:C++ 非常棒,但是 boost 让它变得非常棒! :D
  • 如果我们派生自unordered_map,我们甚至不需要包装类。所以不再需要 children 成员了。更新了我的答案...
猜你喜欢
  • 2019-08-06
  • 1970-01-01
  • 2020-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-27
相关资源
最近更新 更多