std :: eşleme varsayılan değeri


89

Varsayılan değeri belirtmek için bir yolu var mı std::map'ın operator[]bir anahtar yoksa zaman döner?

Yanıtlar:


58

Hayır yok. En basit çözüm, bunu yapmak için kendi ücretsiz şablon işlevinizi yazmaktır. Gibi bir şey:

#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 Güncellemesi

Amaç: Genel ilişkilendirici kapsayıcıların yanı sıra isteğe bağlı karşılaştırıcı ve ayırıcı parametrelerini hesaba katın.

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
Güzel çözüm. İşlev şablonunun, karşılaştırıcı ve ayırıcı için varsayılan şablon parametrelerini kullanmayan haritalarla çalışması için birkaç şablon argümanı eklemek isteyebilirsiniz.
sbi

3
+1, ancak operator[]varsayılan değerle tam olarak aynı davranışı sağlamak için , varsayılan değer if ( it == m.end() )bloğun içindeki haritaya eklenmelidir
David Rodríguez - dribeas

13
@David OP'nin aslında bu davranışı istemediğini varsayıyorum. Yapılandırmaları okumak için benzer bir şema kullanıyorum, ancak bir anahtar eksikse yapılandırmanın güncellenmesini istemiyorum.

2
@GMan bool parametreleri bazıları tarafından kötü stil olarak düşünülür çünkü çağrıya bakarak (bildirimin aksine) ne yaptıklarını söyleyemezsiniz - bu durumda "true", "varsayılanı kullan" veya "don" anlamına gelir varsayılan "(veya tamamen başka bir şey) kullan? Bir enum her zaman daha nettir, ancak elbette daha fazla koddur. Konuyla ilgili iki zihnim var, ben de.

2
Varsayılan değer nullptr ise bu yanıt işe yaramaz, ancak stackoverflow.com/a/26958878/297451 çalışır .
Jon

45

Bu, soruyu tam olarak yanıtlamasa da, aşağıdaki gibi bir kodla sorunu çözdüm:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

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

1
Bu benim için en iyi çözüm. Uygulaması kolay, çok esnek ve genel.
2019

11

C ++ standardı (23.3.1.2), yeni eklenen değerin varsayılan olarak oluşturulduğunu belirtir, bu nedenle mapkendisi bunu yapmanın bir yolunu sağlamaz. Seçimleriniz:

  • Değer türüne, onu istediğiniz değere başlatan varsayılan bir kurucu verin veya
  • Haritayı, varsayılan bir değer sağlayan ve operator[]bu varsayılanı eklemek için uygulayan kendi sınıfınıza sarın .

8
Kesin olmak gerekirse, yeni eklenen değer başlatılan değerdir (8.5.5), bu nedenle: - T, kullanıcı tanımlı bir kurucuya (12.1) sahip bir sınıf tipiyse, T için varsayılan kurucu çağrılır (ve başlatma kötüdür) -T'nin erişilebilir varsayılan kurucusu yoksa biçimlendirilir); - T, kullanıcı tanımlı bir oluşturucu içermeyen bir birleşimsiz sınıf türüyse, T'nin her statik olmayan veri üyesi ve temel sınıf bileşeni değerle başlatılır; - Eğer T bir dizi tipiyse, o zaman her eleman değer olarak başlatılır; - aksi takdirde, nesne sıfır başlatılır
Tadeusz Kopec


6

Daha Genel Sürüm, Destek C ++ 98/03 ve Daha Fazla Konteyner

Genel ilişkisel kapsayıcılarla çalışır, tek şablon parametresi kap türünün kendisidir.

: Desteklenen kaplar std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash, vb

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

Kullanım:

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

Burada yönteme çok benzer olan bir sarmalayıcı sınıf kullanarak benzer uygulamasıdır get()arasında dictPython'da türü 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);
}

Kullanım:

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

MAP :: mapped_type & türü MAP :: mapped_type & defval kapsam dışı olabileceğinden döndürülmesi güvenli değil.
Joe C

5

Varsayılan değeri belirlemenin bir yolu yoktur - her zaman varsayılan tarafından oluşturulan değerdir (sıfır parametre yapıcısı).

Aslında operator[], haritada verilen anahtar için bir değer yoksa, varsayılan kurucudan gelen değerle yeni bir değer ekleyeceği gibi muhtemelen beklediğinizden fazlasını yapar.


2
Doğru, yeni girişler eklemekten kaçınmak findiçin, belirli bir anahtar için herhangi bir öğe yoksa son yineleyiciyi döndüren kullanabilirsiniz .
Thomas Schaub

@ThomasSchaub findBu durumda zaman karmaşıklığı ne kadar ?
ajaysinghnegi

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
Sonra çalışacak şekilde değiştirin. Bu kodun başarmak için tasarlanmadığı bir durumu düzeltmeye zahmet etmeyeceğim.
Thomas Eding

3

Değer, diğer yanıtların söylediği gibi varsayılan kurucu kullanılarak başlatılır. Bununla birlikte, basit türler (int, float, pointer veya POD (eski verileri planla) türleri gibi integral türleri) durumunda, değerlerin sıfır olarak başlatıldığını (veya değer başlatma ile sıfırlandığını (etkin bir şekilde) eklemek yararlıdır. aynı şey), hangi C ++ sürümünün kullanıldığına bağlı olarak).

Her neyse, alt satırda, basit türlere sahip haritaların yeni öğeleri otomatik olarak sıfırlayacağıdır. Bu nedenle bazı durumlarda, varsayılan başlangıç ​​değerini açıkça belirtme konusunda endişelenmenize gerek yoktur.

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

Tür adından sonraki parantezler yeniyle bir fark yaratır mı? Konusuna bakın. konuyla ilgili daha fazla ayrıntı için.


2

Bir geçici çözüm map::at()yerine kullanmaktır []. Anahtar yoksa,at bir istisna atar. Daha da güzel, bu vektörler için de işe yarar ve bu nedenle haritayı bir vektörle değiştirebileceğiniz genel programlama için uygundur.

Kayıtlı olmayan anahtar için özel bir değer kullanmak tehlikeli olabilir, çünkü bu özel değer (-1 gibi) kodda daha aşağı işlenebilir. İstisnalar dışında, hataları tespit etmek daha kolaydır.


1

Belki istediğiniz varsayılan bir değerle tahsis eden özel bir ayırıcı verebilirsiniz.

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

4
operator[]T()ayırıcı ne yaparsa yapsın, çağırarak oluşturulan bir nesne döndürür .
sbi

1
@sbi: Harita ayırıcılar constructyöntemini çağırmıyor mu ? Bunu değiştirmek mümkün olabilir diye düşünüyorum. Yine de, iyi biçimlenmemiş constructdışında bir şey yapan bir işlevden şüpheleniyorum new(p) T(t);. DÜZENLEME: Geriye dönüp baktığımda aptalca, aksi halde tüm değerler aynı olurdu: P
Kahvem

1
@GMan: C ++ 03 kopyam (23.3.1.2'de) operator[]geri dönen diyor (*((insert(make_pair(x, T()))).first)).second. Yani bir şeyi kaçırmıyorsam, bu cevap yanlış.
sbi

Haklısın. Ama bu bana yanlış geliyor. Eklemek için neden ayırıcı işlevini kullanmıyorlar?
VDVLeon

2
@sbi: Hayır, bu cevabın yanlış olduğuna katılıyorum, ancak farklı bir nedenle. Derleyici Gerçekten de öyle insertbir ile T(), ama yeni bir için ayırıcı olsun bellek kullanır zaman iç dolgudur Tsonra çağrı constructolduğunu da verildi parametresi, o bellek T(). Dolayısıyla, davranışını operator[]başka bir şeye döndürmek için değiştirmek gerçekten mümkündür , ancak ayırıcı neden çağrıldığını ayırt edemez. Bu nedenle construct, parametresini göz ardı etsek ve özel değerimizi kullansak bile , bu, inşa edilen her elemanın bu değere sahip olduğu anlamına gelir ki bu kötü.
GManNickG

1

Yanıta Genişleyen https://stackoverflow.com/a/2333816/272642 bu şablon işlevini kullanır std::map'ler key_typeve mapped_typetypedefs tipini anlamak için keyve def. Bu, bu tür yazıların olmadığı kaplarla çalışmaz.

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

Bu, kullanmanıza izin verir

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

gibi argümanlar atmaya gerek kalmadan std::string("a"), (int*) NULL.


1

Kullanım std::map::insert() .

Bu partiye oldukça geç kaldığımı fark ederek, ancak operator[]özel varsayılanların davranışıyla ilgileniyorsanız (yani, verilen anahtara sahip öğeyi bulun, yoksa haritaya bir öğe ekleyin varsayılan değeri seçer ve yeni eklenen değere veya mevcut değere bir başvuru döndürür), C ++ 17 öncesi size sunulan bir işlev zaten var std::map::insert(). insertanahtar zaten mevcutsa gerçekte eklenmez, bunun yerine mevcut değere bir yineleyici döndürür.

Diyelim ki, dizeden int'e bir eşleme istediniz ve anahtar henüz mevcut değilse varsayılan bir 42 değeri ekler:

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 ve 44 çıktı vermelidir.

Harita değerini oluşturmanın maliyeti yüksekse (anahtarın kopyalanması / taşınması veya değer türünün pahalı olması durumunda), bu C ++ 17'lerle atlatılacağını düşündüğüm önemli bir performans cezasına neden olur try_emplace.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.