107

当键不存在时,有没有办法指定默认值std::map的返回?operator[]

4

15 回答 15

64

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

#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;
}
于 2010-02-25T12:10:37.973 回答
59

虽然这并不能完全回答这个问题,但我已经用这样的代码绕过了这个问题:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;
于 2015-04-11T08:32:59.663 回答
13

C++17 提供了try_emplace正是这样做的。它为值构造函数接受一个键和一个参数列表,并返回一对:aniterator和 a bool.: http://en.cppreference.com/w/cpp/container/map/try_emplace

于 2017-07-10T17:22:32.253 回答
11

C++ 标准 (23.3.1.2) 指定新插入的值是默认构造的,因此map它本身并没有提供这样做的方法。您的选择是:

  • 为值类型提供一个默认构造函数,将其初始化为您想要的值,或者
  • 将地图包装在您自己的类中,该类提供默认值并实现operator[]插入该默认值。
于 2010-02-25T12:07:42.957 回答
6

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

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

支持的容器:std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash, 等。

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");

下面是一个类似的实现,使用了一个包装类,更类似于Pythonget()中的type 方法: https ://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hppdict

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");
于 2014-11-16T15:56:45.190 回答
5

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

事实上operator[],它的作用可能比您预期的要多,就好像地图中给定键的值不存在一样,它将插入一个具有默认构造函数值的新值。

于 2010-02-25T12:02:36.230 回答
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;
于 2012-08-08T17:49:00.673 回答
5

正如其他答案所说,该值是使用默认构造函数初始化的。然而,补充一点是有用的,在简单类型的情况下(整数类型,如 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 有区别?有关此事的更多详细信息。

于 2014-03-07T11:49:15.320 回答
2

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

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

于 2019-01-16T17:55:30.117 回答
2

Pre-C++17,使用std::map::insert(),对于较新的版本,使用try_emplace(). 这可能违反直觉,但这些函数实际上具有operator[]自定义默认值的行为。

意识到我参加这个聚会已经很晚了,但是如果您对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()

于 2019-11-04T10:19:23.770 回答
1

也许您可以提供一个自定义分配器,该分配器使用您想要的默认值进行分配。

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;
于 2010-02-25T12:06:45.127 回答
1

扩展答案https://stackoverflow.com/a/2333816/272642,此模板函数使用std::map'skey_typemapped_typetypedefs 来推断 and 的key类型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.

于 2020-09-01T07:34:30.730 回答
1

如果你可以访问 C++17,我的解决方案如下:

std::map<std::string, std::optional<int>> myNullables;
std::cout << myNullables["empty-key"].value_or(-1) << std::endl;

这允许您在每次使用地图时指定“默认值”。这可能不一定是您想要或需要的,但为了完整起见,我会在这里发布。该解决方案非常适合功能范式,因为地图(和字典)通常以这种样式使用:

Map<String, int> myNullables;
print(myNullables["empty-key"] ?? -1);
于 2021-03-23T22:01:49.200 回答
0

使用C++20编写这样的 getter 很简单:

constexpr auto &getOrDefault(const auto &map, const auto &key, const auto &defaultValue)
{
    const auto itr = map.find(key);
    return itr == map.cend() ? defaultValue : itr->second;
}
于 2021-12-22T00:25:42.087 回答
-1

如果您想继续使用operator[],就像您不必指定默认值T()T值类型在哪里)一样,您可以T在构造函数中继承并指定不同的默认值:

#include <iostream>
#include <map>
#include <string>

int main() {
  class string_with_my_default : public std::string {
  public:
    string_with_my_default() : std::string("my default") {}
  };

  std::map<std::string, string_with_my_default> m;

  std::cout << m["first-key"] << std::endl;
}

但是,如果T是原始类型,请尝试以下操作:

#include <iostream>
#include <map>
#include <string>

template <int default_val>
class int_with_my_default {
private:
  int val = default_val;
public:
  operator int &() { return val; }
  int* operator &() { return &val; }
};

int main() {
  std::map<std::string, int_with_my_default<1> > m;

  std::cout << m["first-key"] << std::endl;
  ++ m["second-key"];
  std::cout << m["second-key"] << std::endl;
}

另请参见围绕基本类型的 C++ 类包装器

于 2021-10-07T00:23:49.233 回答