std :: map默认值


85

当键不存在时,是否可以指定默认值std::mapoperator[]返回值?

Answers:


55

不,没有。最简单的解决方案是编写自己的免费模板函数来执行此操作。就像是:

#include <string>
#include <map>
using namespace std;

template <typename K, typename V>
V GetWithDef(const  std::map <K,V> & m, const K & key, const V & defval ) {
   typename std::map<K,V>::const_iterator it = m.find( key );
   if ( it == m.end() ) {
      return defval;
   }
   else {
      return it->second;
   }
}

int main() {
   map <string,int> x;
   ...
   int i = GetWithDef( x, string("foo"), 42 );
}

C ++ 11更新

目的:解释通用的关联容器,以及可选的比较器和分配器参数。

template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
    typename C<K,V,Args...>::const_iterator it = m.find( key );
    if (it == m.end())
        return defval;
    return it->second;
}

1
不错的解决方案。您可能需要添加一些模板参数,以便函数模板可以与不使用默认模板参数作为比较器和分配器的映射一起使用。
2010年

3
+1,但要提供确切相同的行为operator[]与默认值,默认值应该被插入到里面的地图if ( it == m.end() )
大卫·罗德里格斯- dribeas

12
@David我假设OP实际上并不需要这种行为。我使用类似的方案来读取配置,但是如果缺少密钥,我不希望更新配置。

2
@GMan bool参数被认为是一种不好的样式,因为您无法通过查看调用(而不是声明)来判断它们的作用-在这种情况下,“ true”表示“ use default”或“ don”不能使用默认值”(或完全使用其他名称)?枚举总是更清晰,但当然也包含更多代码。我在这个问题上有两种想法,我自己。

2
如果默认值为nullptr,则此答案无效,但是stackoverflow.com/a/26958878/297451可以。
2016年

44

虽然这不能完全回答问题,但我已经使用以下代码规避了这个问题:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;

1
这对我来说是最好的解决方案。易于实施,非常灵活且通用。
acegs

11

C ++标准(23.3.1.2)指定新插入的值是默认构造的,因此map它本身不提供执行此操作的方法。您的选择是:

  • 给值类型一个默认的构造函数,该构造函数将其初始化为所需的值,或者
  • 将地图包装在您自己的提供默认值的类中,并实现operator[]插入该默认值。

8
好吧,准确地说,新插入的值是值初始化的(8.5.5),因此:-如果T是具有用户声明的构造函数(12.1)的类类型,则将调用T的默认构造函数(并且初始化是错误的-如果T没有可访问的默认构造函数,则为- —如果T是一个没有用户声明的构造函数的非联合类类型,则T的每个非静态数据成员和基类组件都将被值初始化;—如果T是数组类型,则每个元素都进行值初始化;—否则,该对象将被初始化为零
Tadeusz Kopec 2010年

6

更通用的版本,支持C ++ 98/03和更多的容器

与通用关联容器一起使用,唯一的模板参数是容器类型本身。

支持的容器:std::mapstd::multimapstd::unordered_mapstd::unordered_multimapwxHashMapQMapQMultiMapQHashQMultiHash,等。

template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m, 
                                             const typename MAP::key_type& key, 
                                             const typename MAP::mapped_type& defval)
{
    typename MAP::const_iterator it = m.find(key);
    if (it == m.end())
        return defval;

    return it->second;
}

用法:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

这是使用包装器类的类似实现,它与Python中get()dict类型方法更相似:https : //github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp

template<typename MAP>
struct map_wrapper
{
    typedef typename MAP::key_type K;
    typedef typename MAP::mapped_type V;
    typedef typename MAP::const_iterator CIT;

    map_wrapper(const MAP& m) :m_map(m) {}

    const V& get(const K& key, const V& default_val) const
    {
        CIT it = m_map.find(key);
        if (it == m_map.end())
            return default_val;

        return it->second;
    }
private:
    const MAP& m_map;
};

template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
    return map_wrapper<MAP>(m);
}

用法:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");

MAP :: mapped_type&不安全返回,因为类型名MAP :: mapped_type&defval可能不在范围内。
乔C


5

无法指定默认值-它始终是由默认值(零参数构造函数)构造的值。

实际上,operator[]可能做得比您预期的要多,好像映射中给定键的值不存在时,它将使用默认构造函数中的值插入一个新值。


2
正确,为避免添加新条目find,如果给定键不存在任何元素,则可以使用它返回结束迭代器。
Thomas Schaub 2010年

@ThomasSchaubfind在这种情况下的时间复杂度是多少?
ajaysinghnegi '19

5
template<typename T, T X>
struct Default {
    Default () : val(T(X)) {}
    Default (T const & val) : val(val) {}
    operator T & () { return val; }
    operator T const & () const { return val; }
    T val;
};

<...>

std::map<KeyType, Default<ValueType, DefaultValue> > mapping;

3
然后对其进行修改,使其起作用。我不会费心去解决这个代码并非旨在解决的问题。
Thomas Eding

3

该值是使用默认构造函数初始化的,如其他答案所述。但是,增加一个有用的补充是,在简单类型的情况下(整数类型,例如int,float,pointer或POD(计划旧数据)类型),将值初始化为零(或通过值初始化为零(有效)同样的事情),具体取决于所使用的C ++版本)。

无论如何,最重要的是,具有简单类型的地图将自动对新项进行零初始化。因此,在某些情况下,无需担心显式指定默认初始值。

std::map<int, char*> map;
typedef char *P;
char *p = map[123],
    *p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0

请参阅类型名称后面的括号是否与new有所区别?有关此问题的更多详细信息。


2

一种解决方法是使用map::at()代替[]。如果键不存在,则at引发异常。更好的是,它也适用于矢量,因此适用于可以将地图与矢量交换的通用编程。

将自定义值用于未注册的密钥可能很危险,因为该自定义值(如-1)可能会在代码中进一步处理。除例外之外,更容易发现错误。


1

也许您可以给自定义分配器分配要使用的默认值。

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;

4
operator[]T()无论分配器做什么,都将返回通过调用创建的对象。
2010年

1
@sbi:映射不调用allocatorsconstruct方法吗?我认为,有可能改变这种状况。我怀疑一个construct函数除了new(p) T(t);格式不正确外没有做其他事情。编辑:事后看来这是愚蠢的,否则所有值都将是相同的:P我的咖啡在哪里...
GManNickG 2010年

1
@GMan:我的C ++ 03副本说(在23.3.1.2中)operator[]返回(*((insert(make_pair(x, T()))).first)).second。因此,除非我缺少任何东西,否则这个答案是错误的。
2010年

你是对的。但这对我来说似乎是错误的。他们为什么不使用分配器函数进行插入?
VDVLeon

2
@sbi:不,我同意这个答案是错误的,但是出于不同的原因。编译器确实insert使用了T(),但是在insert里面是它将使用分配器获取新内存的时间,T然后construct使用给定的参数调用该内存T()。因此,确实有可能更改行为operator[]以使其返回其他内容,但是分配器无法区分被调用的原因。因此,即使我们construct忽略它的参数并使用我们的特殊值,这也意味着每个构造的元素都具有该值,这很糟糕。
GManNickG 2010年

1

扩展答案https://stackoverflow.com/a/2333816/272642时,此模板函数使用std::mapkey_typemapped_typetypedef来推断keyand的类型def。没有这些typedef的容器将无法使用。

template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
    typename C::const_iterator it = m.find(key);
    if (it == m.end())
        return def;
    return it->second;
}

这使您可以使用

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

无需像这样进行争论std::string("a"), (int*) NULL


1

使用std::map::insert()

意识到我参加这个聚会已经很晚了,但是如果您对operator[]自定义默认设置的行为感兴趣(即,找到具有给定键的元素,如果不存在,则在地图中插入带有选择默认值并返回对新插入值或现有值的引用),C ++ 17之前的版本已经可以使用std::map::insert()insert如果键已经存在,则不会实际插入,而是将迭代器返回到现有值。

假设您想要一个字符串到整数的映射,如果还不存在该键,则插入默认值42:

std::map<std::string, int> answers;

int count_answers( const std::string &question)
{
    auto  &value = answers.insert( {question, 42}).first->second;
    return value++;
}

int main() {

    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    return 0;
}

它应该输出42、43和44。

如果构造映射值的成本很高(如果复制/移动键或值类型很昂贵),那么这将导致明显的性能损失,我认为C ++ 17可以避免这种情况try_emplace

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.