Map 默认值

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

119239 次浏览

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

事实上,如果在 map 中给定的键不存在一个值,那么 abc0可能比你预期的要做得更多,它会从缺省构造函数中插入一个新的值。

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

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

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

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

不,没有。最简单的解决方案是编写自己的免费模板函数来完成这项工作。比如:

#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;
}
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;

The value is initialized using the default constructor, as the other answers say. However, it is useful to add that in case of simple types (integral types such as int, float, pointer or POD (plan old data) types), the values are zero-initialized (or zeroed by value-initialization (which is effectively the same thing), depending on which version of C++ is used).

无论如何,底线是,使用简单类型的映射将自动对新项进行零初始化。因此在某些情况下,不需要担心显式地指定默认初始值。

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

有关此事的更多细节,请参见 Do the parentheses after the type name make a difference with new?

更多通用版本,支持 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 中 dict类型的方法 get(): 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");

虽然这并不能准确地回答这个问题,但是我已经用这样的代码规避了这个问题:

struct IntDefaultedToMinusOne
{
int i = -1;
};


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

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

One workaround is to use map::at() instead of []. 如果键不存在,at将引发异常。 更妙的是,这也适用于向量,因此适用于用向量交换地图的泛型。

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

Pre-C++17, use std::map::insert(), for newer versions use try_emplace(). It may be counter-intuitive, but these functions effectively have the behaviour of operator[] with custom default values.

意识到我已经迟到了,但是如果你对 operator[]的自定义默认行为感兴趣(即: 找到带有给定键的元素,如果它没有给 插入提供一个选择的默认值,并返回一个对新插入值或现有值的引用) ,那么在 C + + 17之前已经有一个函数可用了: std::map::insert()。如果键已经存在,insert实际上不会插入,而是将迭代器返回到现有值。

假设,您想要一个 string-to-int 的映射,并且在键还没有出现的情况下插入一个默认值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;
}

which should output 42, 43 and 44.

如果构造 map 值的成本很高(如果复制/移动键或值类型的成本很高) ,那么性能会受到很大影响,而 C + + 17的 try_emplace()可以避免这种影响。

在回答 https://stackoverflow.com/a/2333816/272642的基础上,这个模板函数使用 std::mapkey_typemapped_type typedefs 来推断 keydef的类型。 如果容器没有这些 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那样进行论证。

如果你能使用 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);

如果你想继续使用 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;
}

However, if T is a primitive type, try this:

#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 + + 类包装器

使用 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;
}

下面是一个正确的方法,如果调用方传入对映射类型的左值引用,它将有条件地返回引用。

template <typename Map, typename DefVal>
using get_default_return_t = std::conditional_t<std::is_same_v<std::decay_t<DefVal>,
typename Map::mapped_type> && std::is_lvalue_reference_v<DefVal>,
const typename Map::mapped_type&, typename Map::mapped_type>;


template <typename Map, typename Key, typename DefVal>
get_default_return_t<Map, DefVal> get_default(const Map& map, const Key& key, DefVal&& defval)
{
auto i = map.find(key);
return i != map.end() ? i->second : defval;
}


int main()
{
std::map<std::string, std::string> map;
const char cstr[] = "world";
std::string str = "world";
auto& ref = get_default(map, "hello", str);
auto& ref2 = get_default(map, "hello", std::string{"world"}); // fails to compile
auto& ref3 = get_default(map, "hello", cstr); // fails to compile
return 0;
}