STL haritalarında map :: insert kullanmak [] 'den daha mı iyidir?


201

Bir süre önce, bir meslektaşımla STL haritalarına nasıl değer ekleyeceğimi tartıştım . map[key] = value; Doğal hissettiği ve okuması açık olduğu için tercih ettim , map.insert(std::make_pair(key, value))

Sadece sordum ve ikimiz de eklemenin neden daha iyi olduğunu hatırlayamıyoruz, ama eminim sadece bir stil tercihi değil, verimlilik gibi teknik bir neden de vardı. SGI STL referans basitçe "Açıkçası, bu üye işlev gereksizdir.: O sadece kolaylık var" diyor

Biri bana bu sebebi söyleyebilir mi, yoksa sadece bir tane olduğunu hayal ediyor muyum?


2
Tüm harika yanıtlar için teşekkürler - gerçekten yardımcı oldular. Bu en iyi yığın taşması büyük bir demo. Kabul edilen cevap olması gerektiği için parçalandım: netjeff farklı davranışlar hakkında daha açık, Greg Rogers performans sorunlarından bahsetti. Her ikisini de işaretleyebilseydim.
danio

6
Aslında, C ++ 11 ile, muhtemelen en iyi haritayı kullanıyorsunuz :: emplace , çift yapıyı önler
einpoklum

@einpoklum: Aslında Scott Meyers "Etkili C ++ için gelişen arama" konulu konuşmasında aksini ileri sürüyor.
Thomas Eding

3
@ einpoklum: Yeni inşa edilmiş belleğe yerleşirken durum budur. Ancak, haritaya ilişkin bazı standart gereksinimleri nedeniyle, yerleştirmenin yerleştirmeden daha yavaş olabileceğinin teknik nedenleri vardır. Konuşma, youtube.com/watch?v=smqT9Io_bKo @ ~ 38-40 dk. İşareti gibi youtube'da ücretsiz olarak kullanılabilir . Bir SO bağlantısı için işte stackoverflow.com/questions/26446352/…
Thomas Eding

1
Aslında Meyers'ın sunduğu bazı şeylerle tartışacağım, ama bu yorum dizisinin kapsamının ötesinde ve her neyse, sanırım önceki yorumumu geri çekmek zorundayım.
einpoklum

Yanıtlar:


240

Yazdığın zaman

map[key] = value;

orada ayırt etmenin bir yolu değiştirilirvalue FOR keyveya eğer oluşturulan yeni keyolan value.

map::insert() yalnızca aşağıdakileri oluşturur:

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

Uygulamalarımın çoğu için, genellikle oluşturup oluşturmamayı veya değiştirmem umrumda değil, bu yüzden okumayı daha kolay kullanıyorum map[key] = value.


16
Map :: insert'in asla değerlerin yerine geçmediğine dikkat edilmelidir. Ve genel durumda ben kullanmak daha iyi olduğunu söyleyebilirim (res.first)->secondyerine valueikinci durumda da.
dalle

1
Map :: insert'in asla yer değiştirmediğinden daha açık olacak şekilde güncellendi. Ben sol elsekullanıyorum düşünüyorum çünkü valueyineleyici daha fazla nettir. Yalnızca değerin türünde olağandışı bir kopyalayıcı veya op == olması farklı olurdu ve bu tür, harita gibi STL kapsayıcılarını kullanırken başka sorunlara neden olabilir.
netjeff

1
map.insert(std::make_pair(key,value))olmalı map.insert(MyMap::value_type(key,value)). Döndürülen make_pairtür, alınan insert
türle

1
takıp takmadığınızı ya da sadece atandığınızı anlamanın bir yolu var operator[], sadece boyutu önce ve sonra karşılaştırın. map::operator[]Sadece varsayılan yapılandırılabilir türler için çağrı yapabilmek çok daha önemlidir.
idclev 463035818

@ DavidRodríguez-dribeas: İyi öneri, cevabımdaki kodu güncelledim.
netjeff

53

Haritada zaten mevcut olan anahtar söz konusu olduğunda, ikisinin farklı semantiği vardır. Yani gerçekten doğrudan karşılaştırılamazlar.

Ancak işleç [] sürümü varsayılan olarak değeri oluşturmayı ve sonra atamayı gerektirir, bu nedenle bu kopya kopyadan daha pahalıysa, daha pahalı olacaktır. Bazen varsayılan yapı anlamsızdır ve operatör [] sürümünü kullanmak imkansızdır.


1
make_pair bir kopya oluşturucu gerektirebilir - bu varsayılandan daha kötü olabilir. Yine de +1.

1
Ana şey, dediğin gibi, farklı semantiklere sahip olmaları. Yani ikisi de diğerinden daha iyi değil, sadece ihtiyacınız olanı kullanın.
jalf

Kopya oluşturucu neden varsayılan kurucudan sonra atamadan sonra daha kötü olur? Eğer öyleyse, o zaman sınıfı yazan kişi bir şeyi kaçırdı, çünkü operatör = ne yaparsa yapsın, kopya oluşturucuda da aynısını yapmalıydı.
Steve Jessop

1
Bazen varsayılan oluşturma, ödevin kendisi kadar pahalıdır. Doğal olarak atama ve kopya yapımı eşdeğer olacaktır.
Greg Rogers

@Arkadiy Optimize edilmiş bir derlemede, derleyici genellikle gereksiz birçok kopya oluşturucu çağrısını kaldıracaktır.
antred

35

Dikkat edilmesi gereken başka bir şey std::map:

myMap[nonExistingKey];haritada nonExistingKeyvarsayılan bir değere sıfırlanmış yeni bir giriş oluşturur .

Bu, ilk gördüğümde cehennemi korkuttu (başımı çok eski bir hataya karşı vurarak). Bunu beklemezdim. Bana göre, bu bir get işlemi gibi görünüyor ve "yan etki" beklemiyordum. map.find()Haritanızdan almayı tercih edin.


3
Karma haritalar bu biçim için oldukça evrensel olmasına rağmen, bu iyi bir görünümdür. Aynı sözleşmeleri ne kadar yaygın kullandıklarından dolayı kimsenin garip olmadığını düşündüğü tuhaflıklardan biri olabilir
Stephen J

19

Varsayılan kurucunun performans isabeti bir sorun değilse, lütfen, tanrı sevgisi için daha okunabilir versiyona gidin.

:)


5
İkinci! Bunu işaretlemelisin. Çok fazla insan nano saniye hızlandırmaları için açıklıktan ödün veriyor. Bize bu tür zulümleri sürdürmesi gereken fakir ruhlara merhamet et!
Mr.Ree

6
Greg Rogers'ın yazdığı gibi: "Haritada zaten mevcut olan anahtar söz konusu olduğunda, bu ikisinin farklı semantiği var. Yani gerçekten doğrudan karşılaştırılamazlar."
dalle

Bu eski bir soru ve cevap. Ancak "daha okunabilir versiyon" aptalca bir nedendir. Çünkü en okunabilir olan kişiye bağlıdır.
Vallentin

14

insert istisna güvenlik açısından daha iyidir.

İfade map[key] = valueaslında iki işlemdir:

  1. map[key] - varsayılan değere sahip bir harita öğesi oluşturma.
  2. = value - değerin o öğeye kopyalanması.

İkinci adımda bir istisna olabilir. Sonuç olarak, işlem sadece kısmen yapılacaktır (haritaya yeni bir eleman eklenmiştir, ancak bu eleman ile başlatılmamıştır.value ). Bir işlemin tamamlanmadığı, ancak sistem durumunun değiştirildiği duruma "yan etki" ile işlem denir.

insertoperasyon güçlü bir garanti verir, yan etkileri olmadığı anlamına gelir ( https://en.wikipedia.org/wiki/Exception_safety ). insertya tamamen yapılır ya da haritayı değiştirilmemiş durumda bırakır.

http://www.cplusplus.com/reference/map/map/insert/ :

Tek bir eleman takılacaksa, istisna olması durumunda kapta değişiklik olmaz (güçlü garanti).


1
daha da önemlisi, insert varsayılan olarak yapılandırılabilir olmak için değer gerektirmez.
UmNyobe

13

Uygulamanız hız açısından kritikse, orijinal nesnenin 2 tanesi geçici nesne olan ve er ya da geç olarak imha edilen toplam 3 kopyasını oluşturduğu için [] operatörünü kullanmanızı öneririm.

Ancak insert () 'de, orijinal nesnenin 4 kopyası oluşturulur ve bunların 3 tanesi geçici nesnelerdir (mutlaka "geçici" değil) ve yok edilir.

Bu, aşağıdakiler için ekstra zaman anlamına gelir: 1. Bir nesne bellek ayırma 2. Bir ekstra kurucu çağrı 3. Bir ekstra yıkıcı çağrı 4. Bir nesne bellek ayırma

Nesneleriniz büyükse, yapıcılar tipiktir, yıkıcılar çok fazla kaynak serbest bırakır, yukarıdaki noktalar daha da önemlidir. Okunabilirlik konusunda, her ikisinin de yeterince adil olduğunu düşünüyorum.

Aynı soru aklıma geldi ama aşırı okunabilirlik değil, hız. Burada bahsettiğim nokta hakkında bilmek için geldi bir örnek kod.

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

İnsert () kullanıldığında çıktı [] Operatörü kullanıldığında çıktı


4
Şimdi bu testi tam optimizasyonlar etkinken tekrar çalıştırın.
antred

2
Ayrıca, [] operatörünün gerçekte ne yaptığını düşünün. Önce haritada belirtilen anahtarla eşleşen bir giriş arar. Birini bulursa, o girişin değerini belirtilen değerle değiştirir. Başlamazsa, belirtilen anahtar ve değerle yeni bir giriş ekler. Haritanız ne kadar büyük olursa, haritayı aramak operatörün [] süresi o kadar uzun sürer. Bir noktada, bu ekstra bir kopya oluşturucu çağrısını telafi etmekten daha fazla olacaktır (eğer derleyici optimizasyon sihrini yaptıktan sonra son programda kalırsa).
antred

1
@antred, insertaynı aramayı yapmak zorunda, bu yüzden hiçbir fark yok [](çünkü harita anahtarları benzersizdir).
Sz.

Çıktılarda neler olup bittiğinin güzel bir örneği - ama bu 2 kod parçası aslında ne yapıyor? Orijinal nesnenin 3 kopyası neden gereklidir?
rbennett485

1. atama operatörü uygulamak gerekir, ya da yıkıcı yanlış numaraları alırsınız 2. aşırı kopya oluşturma "map.insert (std :: make_pair <int, Sample> (1, std: : taşı (örnek))); "
Fl0

10

Şimdi c ++ 11 bir STL haritası bir çift eklemek için en iyi yolu olduğunu düşünüyorum:

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

Sonuç ile bir çift olacaktır:

  • İlk öğe (sonuç.İlk), eklenen çifti işaret eder veya anahtar zaten varsa bu anahtarla çifti işaret eder.

  • İkinci öğe (sonuç.saniye), ekleme doğru veya yanlışsa doğru bir şey ters gitti.

PS: sipariş hakkında dava yoksa std :: unordered_map;)

Teşekkürler!


9

Map :: insert () içeren bir gotcha, anahtar haritada zaten varsa bir değerin yerini almaz. Java programcıları tarafından yazıldıkları yerde Java'nın Map.put () ile aynı şekilde davranmasını bekledikleri değerlerin değiştirildiği C ++ kodunu gördüm.


2

Bir not da kullanabilirsiniz olmasıdır Boost.Assign :

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}

1

Burada , anahtar varsa değerin üzerine yazıldığını ancak varsa değerin operator[] üzerine yazılmadığını gösteren başka bir örnek verilmiştir ..insert

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

1

Bu oldukça kısıtlı bir durum, ancak aldığım yorumlardan yola çıkarak kayda değer olduğunu düşünüyorum.

Geçmişte insanların harita biçiminde kullandıklarını gördüm

map< const key, const val> Map;

yanlışlıkla değer üzerine yazma durumlarından kaçınmak, ancak daha sonra başka bazı kod parçalarında yazmaya devam etmek için:

const_cast< T >Map[]=val;

Hatırladıkları gibi bunu yapma nedenleri, bu belirli kod parçalarında harita değerlerinin üzerine yazmayacaklarından emindiler; dolayısıyla, daha 'okunabilir' yöntemle devam etmek[] .

Aslında bu insanlar tarafından yazılan koddan herhangi bir doğrudan sorun yaşamadım, ancak bugüne kadar risklerin - ne kadar küçük olsa da - kolayca önlenebilecekleri zaman alınmaması gerektiğini hissediyorum.

Üzerine kesinlikle yazılmaması gereken harita değerleri ile uğraşıyorsanız kullanın insert. Yalnızca okunabilirlik için istisna yapmayın.


Birden fazla kişi bunu yazdı mı? Kesinlikleinsert (değil input) kullanın const_cast, çünkü önceki değerin üzerine yazılmasına neden olur, ki bu çok sabit değildir. Veya değer türünü işaretlemeyin const. (Bu tür şeyler genellikle nihai sonucudur const_cast, bu yüzden neredeyse her zaman başka bir yerde bir hatayı gösteren kırmızı bir bayraktır.)
Potatoswatter

@Potatoswatter Haklısın. Ben sadece bazı kod bitlerinde eski bir değeri değiştirmeyeceklerinden emin olduklarında bazı insanlar tarafından const_cast [] const harita değerleri ile kullanıldığını görüyorum; çünkü [] kendisi daha okunabilir. Cevabımın son kısmında bahsettiğim gibi, insertdeğerlerin üzerine yazılmasını önlemek istediğiniz durumlarda kullanmanızı tavsiye ederim . (Sadece değişti inputiçin insert- sayesinde)
dk123

@Potatoswatter Doğru hatırlıyorsam, insanların kullandıkları ana nedenler const_cast<T>(map[key])1 idi. [] Daha okunabilir, 2. değerlerin üzerine yazamayacakları belirli kod parçalarına güveniyorlar ve 3. değerlerinin üzerine yazılmayan kodun diğer bitlerini isteyin - dolayısıyla const value.
dk123

2
Bunun yapıldığını hiç duymadım; Bunu nerede gördün? Yazma const_cast, ekstra "okunabilirliğini" reddetmekten daha fazlası gibi görünüyor []ve bu tür bir güven, bir geliştiriciyi ateşlemek için neredeyse yeterli bir neden. Zor çalışma süresi koşulları, bağırsak duyguları ile değil, kurşun geçirmez tasarımlarla çözülür.
Potatoswatter

@Potatoswatter Eğitsel oyunlar geliştiren geçmiş işlerimden biri olduğunu hatırlıyorum. Kodu yazan insanların alışkanlıklarını değiştirmelerini asla sağlayamadım. Kesinlikle haklısın ve sana kesinlikle katılıyorum. Yorumlarınızdan, bunun muhtemelen orijinal cevabımdan daha değerli olduğuna karar verdim ve bu yüzden bunu yansıtmak için güncelledim. Teşekkürler!
dk123

1

Std :: map insert()işlevinin anahtarla ilişkili değerin üzerine yazmaması gerçeği, nesne numaralandırma kodunu şu şekilde yazmamızı sağlar:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

Farklı benzersiz olmayan nesneleri 0..N aralığındaki bazı kimliklerle eşleştirmemiz gerektiğinde oldukça yaygın bir sorundur. Bu kimlikler daha sonra örneğin grafik algoritmalarında kullanılabilir. operator[]Bence alternatif daha az okunabilir görünecektir:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
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.