std :: map 기본값


85

키가 존재하지 않을 때 기본값 std::mapoperator[]반환 을 지정하는 방법 이 있습니까?

답변:


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
좋은 솔루션입니다. 함수 템플릿이 비교 자와 할당 자에 대한 기본 템플릿 매개 변수를 사용하지 않는 맵에서 작동하도록 몇 가지 템플릿 인수를 추가 할 수 있습니다.
sbi

3
+1,하지만 operator[]기본값과 똑같은 동작을 제공하려면 기본값을 if ( it == m.end() )블록 내부의 맵에 삽입해야합니다.
David Rodríguez-dribeas

12
@David 나는 OP가 실제로 그 행동을 원하지 않는다고 가정하고 있습니다. 구성을 읽는 데 유사한 체계를 사용하지만 키가없는 경우 구성을 업데이트하고 싶지 않습니다.

2
@GMan bool 매개 변수는 호출 (선언과는 반대로)을 보면 알 수 없기 때문에 잘못된 스타일로 간주됩니다.이 경우 "true"는 "기본값 사용"또는 "don"을 의미합니다. t 기본값 사용 "(또는 완전히 다른 것)? 열거 형은 항상 더 명확하지만 물론 더 많은 코드입니다. 나는 주제에 대해 두 가지 생각을 가지고 있습니다.

2
이 답변은 기본값이 nullptr 인 경우 작동 하지 않지만 stackoverflow.com/a/26958878/297451 은 작동합니다.
Jon

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가 배열 유형이면 각 요소는 값이 초기화됩니다. — 그렇지 않으면 객체가 0으로 초기화됩니다.
Tadeusz Kopec

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

다음은 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 &은 typename MAP :: mapped_type & defval이 범위를 벗어날 수 있으므로 반환하기에 안전하지 않습니다.
Joe C


5

기본값을 지정하는 방법은 없습니다. 항상 기본값 (제로 매개 변수 생성자)에 의해 구성된 값입니다.

실제로 operator[]맵에 주어진 키에 대한 값이 존재하지 않는 것처럼 기본 생성자의 값으로 새 키를 삽입하는 것처럼 예상보다 더 많은 작업을 수행 할 수 있습니다.


2
맞습니다 find. 주어진 키에 대한 요소가 없으면 끝 반복자를 반환 하는 새 항목을 추가하지 않으려면 사용할 수 있습니다 .
Thomas Schaub

@ThomasSchaub이 경우의 시간 복잡성은 얼마 find입니까?
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 (plan old data) 유형과 같은 통합 유형)의 경우 값이 0으로 초기화됩니다 (또는 값 초기화에 의해 0으로 처리된다는 점을 추가하는 것이 유용합니다. 같은 것), 사용되는 C ++ 버전에 따라 다름).

어쨌든 결론은 단순한 유형의 맵이 새 항목을 자동으로 0으로 초기화한다는 것입니다. 따라서 어떤 경우에는 기본 초기 값을 명시 적으로 지정하는 것에 대해 걱정할 필요가 없습니다.

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()할당자가 무엇을하든 을 호출하여 생성 된 객체를 반환합니다 .
sbi

1
@sbi : 맵이 할당 자 construct메서드를 호출하지 않습니까? 그것을 바꿀 수 있다고 생각합니다. 그래도 제대로 구성되지 않은 construct것 이외의 다른 기능을 수행하는 것 같습니다 new(p) T(t);. 편집 : 어리석은 생각에, 그렇지 않으면 모든 값이 동일 할 것입니다 : P 내 커피는 어디에 ...
GManNickG

1
@GMan는 : 03 ++ C의 내 사본 (23.3.1.2에) 있다고 operator[]반환 (*((insert(make_pair(x, T()))).first)).second. 그래서 내가 뭔가를 놓치고 있지 않는 한이 대답은 틀렸다.
sbi

당신이 옳습니다. 그러나 그것은 나에게 잘못된 것 같습니다. 삽입을 위해 할당 자 함수를 사용하지 않는 이유는 무엇입니까?
VDVLeon

2
@sbi : 아니요,이 답변이 틀렸다는 데 동의하지만 다른 이유가 있습니다. 컴파일러는 실제로 수행 insert로모그래퍼 T()하지만 새로운에 대한 할당 get 및 메모리를 사용할 때 내부에 삽입은 T다음 호출 construct이다가 주어진 매개 변수와 그 메모리에 T(). 따라서 실제로 operator[]다른 것을 반환 하도록 동작을 변경할 수 있지만 할당자는 호출되는 이유를 구별 할 수 없습니다. 그래서 우리 construct가 매개 변수를 무시하고 우리의 특별한 값을 사용 한다고하더라도 , 그것은 모든 생성 된 요소가 그 값을 가지고 있다는 것을 의미 합니다 .
GManNickG

1

https://stackoverflow.com/a/2333816/272642 답변을 확장 하면이 템플릿 함수는 std::mapkey_typemapped_typetypedef를 사용 하여 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.


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 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.