c ++ haritasına emplace vs operatör [] ekle


191

Haritaları ilk kez kullanıyorum ve bir eleman eklemenin birçok yolu olduğunu fark ettim. Sen kullanabilirsiniz emplace(), operator[]ya da insert()artı kullanmak gibi varyantları value_typeveya make_pair. Hepsi hakkında çok fazla bilgi ve belirli durumlar hakkında sorular olsa da, büyük resmi hala anlayamıyorum. Benim iki sorum:

  1. Her birinin diğerlerine göre avantajı nedir?

  2. Standarda emplace eklemeye gerek var mıydı? Onsuz daha önce mümkün olmayan bir şey var mı?


1
Yerleştirme semantiği, açık dönüşümlere ve doğrudan başlatmaya izin verir.
Kerrek SB

3
Şimdi operator[]dayanmaktadır try_emplace. Bahsetmeye değer olabilir insert_or_assign.
FrankHB

@FrankHB siz (veya bir başkası) güncel bir cevap eklerse, kabul edileni değiştirebilirim.
Alman Capuano

Yanıtlar:


228

Bir haritanın özel durumunda, eski seçenekler sadece iki idi: operator[]ve insert(farklı lezzetleri insert). Onları açıklamaya başlayacağım.

Bu operator[]bir bul veya ekle operatörüdür. Haritanın içinde verilen anahtarın bulunduğu bir öğe bulmaya çalışır ve varsa depolanan değere bir başvuru döndürür. Başlamazsa, varsayılan başlatma ile yerine eklenen yeni bir öğe oluşturur ve bu öğeye bir başvuru döndürür.

insert(Tek eleman lezzet) işlevi alır value_type( std::pair<const Key,Value>), anahtar (kullanır firstüyesi) ve eklemek için çalışır. Çünkü std::mapçiftleri için izin vermez bir şey eklemek böyle olmaz varolan eleman varsa.

İkisi arasındaki ilk fark, operator[]varsayılan bir başlangıç değeri oluşturabilmesi gerektiğidir ve bu nedenle varsayılan olarak başlatılamayan değer türleri için kullanılamaz. İkisi arasındaki ikinci fark, verilen tuşa sahip bir öğe olduğunda ne olacağıdır. insertÖzelliği haritanın durumunu değiştirmek, ancak bunun yerine elemanının (ve bir yineleyici döndürmez falsetakılı değilse belirten).

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

insertTartışma durumunda, value_typefarklı şekillerde oluşturulabilen bir nesnedir . Doğrudan istediğiniz tipte olabilir veya value_typeinşa edilebilecek herhangi bir nesneyi, std::make_pairoyuna girdiği herhangi bir nesneyi geçirebilirsiniz , çünkü std::pairmuhtemelen istediğiniz şey olmasa da, nesnelerin basit bir şekilde oluşturulmasına izin verir ...

Aşağıdaki çağrıların net etkisi benzerdir :

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

Ama aslında aynı değil ... [1] ve [2] aslında eşdeğerdir. Her iki durumda da kod, aynı türden ( std::pair<const K,V>) geçici bir nesne oluşturur ve onu insertişleve iletir. insertFonksiyonu ikili arama ağacında uygun bir düğüm oluşturmak ve sonra kopyalar value_typedüğüme argüman gelen kısmını. Kullanmanın avantajı value_type, value_typeher zaman eşleşir value_type , std::pairargümanların türünü yanlış yazamamanızdır !

Fark [3] 'te. İşlev std::make_pair, a oluşturacak bir şablon işlevidir std::pair. İmza:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

std::make_pairOrtak kullanım olduğu için şablon argümanlarını kasıtlı olarak sağlamadım. Ve ima, şablon argümanlarının çağrıdan çıkarılmasıdır, bu durumda olmak üzere T==K,U==V, çağrının std::make_pairbir döndürür std::pair<K,V>(eksik olduğuna dikkat edin const). İmza value_type, yakın ancak çağrının döndürdüğü değerle aynı olmamasını gerektirir std::make_pair. Yeterince yakın olduğu için, doğru türde bir geçici oluşturur ve kopyayı başlatır. Bu da toplam iki kopya oluşturarak düğüme kopyalanır.

Bu, şablon bağımsız değişkenleri sağlanarak düzeltilebilir:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

Ancak bu yine de, [1] örneğinde açıkça yazılanla aynı şekilde hataya açıktır.

Bu noktaya kadar, harici olarak insertyaratılmasını value_typeve o nesnenin konteynere kopyalanmasını gerektiren farklı çağrı yollarımız var . Alternatif olarak operator[], tür varsayılan olarak yapılandırılabilir ve atanabilirse (yalnızca kasıtlı olarak odaklanarak m[k]=v) ve bir nesnenin varsayılan olarak başlatılmasını ve değerin o nesneye kopyalanmasını gerektirir .

C ++ 11'de, değişken şablonlar ve mükemmel yönlendirme ile, bir kabın içine yerleştirerek (yerinde oluşturma) öğeler eklemenin yeni bir yolu vardır . emplaceFarklı kaplarda fonksiyonları temelde aynı şeyi yapmak: yerine almanın kaynağını ila kopyalamak kabın içine, fonksiyon kapta saklanan nesnenin yapıcı iletilir parametreleri alır.

m.emplace(t,u);               // 5

[5] 'de std::pair<const K, V>, yaratılmaz ve iletilmez emplace, bunun yerine tve unesnesine yapılan başvurular, emplacebunları value_typeveri yapısı içindeki alt nesnenin yapıcısına iletir . Bu durumda , C ++ 03 alternatiflerinin avantajı olan hiçbir kopyası std::pair<const K,V>yapılmaz emplace. Durumunda olduğu gibi inserto haritadaki değerini geçersiz kılmaz.


Düşünmediğim ilginç bir soru, emplacebir harita için gerçekte nasıl uygulanabileceğidir ve bu, genel durumda basit bir sorun değildir.


5
Bu yanıtta ima edilir, ancak map [] = val varsa bir önceki değerin üzerine yazacaktır.
dk123

benim için daha ilginç bir soru, çok az amaca hizmet etmesi. Çift kopyasını kaydettiğiniz için, bu iyi çünkü çift kopya yok, hiçbir mapped_typekopya yok demektir . İstediğimiz mapped_typeşey, çiftin içindeki yapıyı ve haritadaki çift yapısını yerleştirmektir. Bu nedenle, std::pair::emplaceişlev ve yönlendirme desteği map::emplaceeksik. Şu anki haliyle, çift yapıcıya bir kez kopyalanacak şekilde yapılandırılmış bir mapped_type vermeniz gerekir. iki katından daha iyi, ama yine de iyi değil.
v.oddou

aslında bu yorumu değiştiriyorum, C ++ 11'de 1 argüman yapımı durumunda emplike olmaktan tamamen aynı amaca hizmet eden bir şablon çifti yapıcısı var. ve bazı garip parçalı yapı, dedikleri gibi, argümanları iletmek için tupler kullanarak, göründüğü kadar mükemmel bir yönlendirme yapabiliriz.
v.oddou

Görünüşe göre unordered_map ve haritada bir performans hatası var gibi: bağlantı
Deqing

1
Bunu , mevcut yöntemlerden işlevsellikteki bazı boşlukları doldurmaya yardımcı olan insert_or_assignve try_emplace(her ikisi de C ++ 17'den gelen) bilgilerle güncellemek güzel olabilir .
ShadowRanger

15

Emplace: Önceden oluşturduğunuz gerçek nesneleri kullanmak için rvalue referansından yararlanır. Bu, LARGE nesneleri için iyi bir kopyalama veya taşıma yapıcısı çağrılmadığı anlamına gelir! O (log (N)) süresi.

Ekle: Standart lvalue referansı ve rvalue referansı için aşırı yüklenmelere ek olarak, eklenecek öğe listelerini yineleyiciler ve bir öğenin ait olduğu konuma ilişkin "ipuçları" içerir. Bir "ipucu" yineleyici kullanımı zaman ekleme sabit zaman aşağı getirebilir, aksi takdirde O (log (N)) zamanı.

Operatör []: Nesnenin var olup olmadığını kontrol eder ve eğer varsa, bu nesneye yapılan başvuruyu değiştirir, aksi takdirde iki nesne üzerinde make_pair öğesini çağırmak için sağlanan anahtarı ve değeri kullanır ve sonra ekleme işleviyle aynı işi yapar. O (log (N)) zamanı.

make_pair: Bir çift yapmaktan daha fazlasını yapar.

Standarda emplace eklemek için herhangi bir "ihtiyaç" yoktu. C ++ 11'de && tür referansın eklendiğine inanıyorum. Bu, hareket semantiği ihtiyacını ortadan kaldırdı ve bazı belirli bellek yönetimi türlerinin optimizasyonuna izin verdi. Özellikle, değer referansı. Aşırı yüklenmiş insert (değer_türü &&) operatörü, in_place anlambiliminden yararlanmaz ve bu nedenle çok daha az verimlidir. Değer referansları ile başa çıkma kabiliyeti sağlarken, nesnelerin inşasında bulunan temel amaçlarını göz ardı eder.


4
" Standarda emplace eklemek için" ihtiyaç "yoktu." Bu kesinlikle yanlış. emplace()kopyalanamayan veya taşınamayan bir öğe eklemenin tek yoludur. (& evet, belki de, böyle bir şey varsa, kopyala ve taşı yapıcıları inşaattan çok daha pahalı olanı en verimli şekilde eklemek için) Aynı zamanda fikri yanlış anladınız: önceden oluşturduğunuz gerçek nesneleri kullanmak için "; hiçbir nesne henüz oluşturulur, & yönlendirmek mapargümanlar o kendisi içine oluşturması gerekir. Sen nesneyi yapmıyorsun.
underscore_d

10

Optimizasyon fırsatları ve daha basit sözdiziminin yanı sıra, ekleme ve yerleştirme arasındaki önemli bir ayrım, ikincisinin açık dönüşümlere izin vermesidir . (Bu, yalnızca haritalar için değil, standart kütüphanenin tamamında bulunur.)

Göstermek için bir örnek:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

Kuşkusuz bu çok özel bir ayrıntıdır, ancak kullanıcı tanımlı dönüşüm zincirleriyle uğraşırken bunu akılda tutmaya değer.


Foo'nun sandıkta bir yerine iki tane int gerektirdiğini düşünün. Bu çağrıyı kullanabilir misiniz? v.emplace(v.end(), 10, 10); ... ya da artık kullanıma gerekir: v.emplace(v.end(), foo(10, 10) ); ?
Kaitain

Şu anda bir derleyiciye erişimim yok, ancak bunun her iki sürümün de çalışacağı anlamına geleceğini varsayacağım. Gördüğünüz hemen hemen tüm örnekler emplace, tek bir parametre alan bir sınıfı kullanır. IMO, örneklerde birden fazla parametre kullanılmışsa, emplace değişkeninin sözdiziminin doğasını daha açık hale getirecektir.
Kaitain

9

Aşağıdaki kod, birbirinden nasıl insert()farklı olduğunun "büyük resim fikrini" anlamanıza yardımcı olabilir emplace():

#include <iostream>
#include <unordered_map>
#include <utility>

//Foo simply outputs what constructor is called with what value.
struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.

  Foo() { val = foo_counter++;
    std::cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    std::cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    std::cout << "Foo(Foo &) with val:           " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    std::cout << "Foo(const Foo &) with val:     " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    std::cout << "Foo(Foo&&) moving:             " << f2.val
              << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }

  Foo& operator=(const Foo& rhs) {
    std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
              << " \tcalled with lhs.val = \t" << val
              << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val;
    return *this;
  }

  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
};

int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
namespace std {
   template<> struct hash<Foo> {
       std::size_t operator()(const Foo &f) const {
           return std::hash<int>{}(f.val);
       }
   };
}

int main()
{
    std::unordered_map<Foo, int> umap;  
    Foo foo0, foo1, foo2, foo3;
    int d;

    //Print the statement to be executed and then execute it.

    std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
    umap.insert(std::pair<Foo, int>(foo0, d));
    //Side note: equiv. to: umap.insert(std::make_pair(foo0, d));

    std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
    //Side note: equiv. to: umap.insert(std::make_pair(foo1, d));

    std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
    std::pair<Foo, int> pair(foo2, d);

    std::cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    std::cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);

    std::cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    std::cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    std::cout.flush();
}

Aldığım çıktı:

Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

Dikkat:

  1. Her unordered_mapzaman, dahili olarak Foonesneleri (ve diyelim ki Foo *) anahtarlar olarak depolar ; bunların hepsi yok edildiğinde unordered_mapyok edilir. Burada unordered_mapiç anahtarlar foos 13, 11, 5, 10, 7 ve 9'du.

    • Yani teknik olarak, unordered_mapaslında std::pair<const Foo, int>nesneleri depolar , bu da Foonesneleri depolar . Ancak, emplace()farklı olanın "büyük resim fikrini" anlamak için insert()(aşağıdaki vurgulanan kutuya bakın), bu nesneyi geçicistd::pair olarak tamamen pasif olarak hayal etmek uygundur . Bu "büyük resim fikrini" anladıktan sonra, bu aracı std::pairnesnenin unordered_mapkullanımının ince ama önemli teknikler getirerek nasıl yedeklendiğini ve anlanması önemlidir.
  2. Her takma foo0, foo1ve foo2birine 2 arama gerekli Foobireyin kopyalama / taşıma yapıcılar ve 2 çağrı Foo'destructor lar (şimdi açıklamak gibi):

    a. Her birinin yerleştirilmesi foo0ve imha edicinin yerleştirme tamamlandıktan hemen sonra çağrıldığı foo1geçici bir nesne ( foo4ve foo6sırasıyla) oluşturması. Buna ek olarak, unordered_map'in dahili Foos ( Foos ve 5 ve 7 olan), yıkıcıları da unordered_map yok edildiğinde çağrıldı.

    b. Takmak için foo2, bunun yerine ilk açıkça (adında olmayan geçici çifti nesnesini oluşturan pairdenir), Fooüzerinde 'in kopya kurucu foo2(yaratma foo8bir iç üyesi olarak pair). Daha sonra insert()bu çifti unordered_mapdüzenledik foo8, bu da kendi dahili kopyasını ( foo9) oluşturmak için kopya kurucuyu tekrar çağırmaya (açık ) neden oldu . Gibi foos 0 ve 1, sonuçta tek fark bu sokulması için iki yıkıcı aramaları yapıldı foo8biz sonuna ulaştığı zaman bireyin yıkıcı sadece denilen main()yerine hemen sonra çağrılan insert()bitmiş.

  3. Kullanmak foo3sadece 1 kopyala / taşı yapıcı çağrısıyla ( foo10dahili olarak içinde oluşturma unordered_map) ve sadece 1 Foo'in yıkıcı çağrısıyla sonuçlandı. (Buna daha sonra geri döneceğim).

  4. İçin foo11, doğrudan için tam sayı 11 geçirilir emplace(11, d), böylece unordered_mapçağırır Foo(int)getirilmesini, kendi içinde iken kurucu emplace()yöntemi. (2) ve (3) 'ten farklı olarak, bunu fooyapmak için önceden var olan bir nesneye bile ihtiyacımız yoktu . Önemlisi, bir kurucuya yalnızca 1 çağrının Foogerçekleştiğine (ki bu yaratıldı foo11) dikkat edin.

  5. Daha sonra, 12 tamsayısını doğrudan geçtik insert({12, d}). Bunun tersine emplace(11, d)(bir hatırlatıcı sadece bir Fookurucu çağrısıyla insert({12, d})sonuçlandı ), bu çağrı Foo'yapıcısına (yaratma foo12ve foo13) iki çağrıyla sonuçlandı .

Arasındaki ana "büyük resmi" ne fark Bu gösteriler insert()ve emplace()geçerli:

insert() Neredeyse her zaman kullanmak, her zaman bir kurucuya yapılan herhangi bir çağrı tamamen dahili olarak (yani, metod tanımının kapsamı dahilinde ) yapılırsa, kapsamı içinde bir Foonesnenin inşasını veya varlığını gerektirir main()(ardından bir kopyala veya taşıdır). İletdiğiniz anahtarın bağımsız değişkenleri doğrudan tanımlayıcı içindeki bir yapıcı çağrısına yönlendirilir (isteğe bağlı ek ayrıntılar: burada yeni oluşturulan nesne hemen üye değişkenlerinden birine dahil edilir, böylece hiçbir yıkıcı çağrılmaz. yürütme bırakır ve taşıma veya kopyalama yapıcıları çağrılmaz).emplace()Foounordered_mapemplace()emplace()Foounordered_map::emplace()unordered_mapemplace()

Not: Yukarıdaki " neredeyse " in " hemen hemen her zaman " ın nedeni aşağıda I) 'de açıklanmaktadır.

  1. devam etti: görüşmesi özelliğini nedeni umap.emplace(foo3, d)olarak adlandırılan Foo'ın const olmayan kopya yapıcısı şudur: Kullandığımız bu yana emplace(), derleyici olduğunu bilir foo3(bir const olmayan Foobir nesne) bazı bir argüman olması gerekiyordu Fooyapıcısı. Bu durumda, en uygun Fookurucu, sabit olmayan kopya kurucusudur Foo(Foo& f2). Bu yüzden umap.emplace(foo3, d)kopya oluşturucu olarak adlandırılmadı umap.emplace(11, d).

Son Söz:

I. Bir aşırı yükün insert()aslında eşdeğer olduğuna dikkat edin emplace() . Bu cppreference.com sayfasında açıklandığı gibi , bu cppreference.com sayfasının aşırı template<class P> std::pair<iterator, bool> insert(P&& value)yüklenmesi (2'nin aşırı yüklenmesi (2) insert()) ile eşdeğerdir emplace(std::forward<P>(value)).

II. Buradan nereye gidilir?

a. Yukarıdaki kaynak kodu ile oynayın ve çevrimiçi bulunan insert()(örneğin, burada ) ve emplace()(örneğin, burada ) için belgeleri inceleyin . Eclipse veya NetBeans gibi bir IDE kullanıyorsanız, IDE'nizi hangi aşırı yüklenmenin insert()veya emplace()çağrıldığını size söylemesi için kolayca alabilirsiniz (eclipse'de farenizin imlecini işlev çağrısı üzerinde bir saniyeliğine sabit tutun). İşte denemek için biraz kod daha:

std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));

Yakında hangi aşırı yükleyicinin std::pair( referansa bakın ) tarafından kullanıldığını göreceksiniz unordered_map, kaç nesnenin kopyalandığı, taşındığı, yaratıldığı ve / veya yok edildiği ve bunların hepsinin ne zaman meydana geldiği üzerinde önemli bir etkisi olabilir.

b. Bunun yerine başka bir kapsayıcı sınıfı (ör. std::setVeya std::unordered_multiset) kullandığınızda ne olacağını görün std::unordered_map.

c. Şimdi, a türü yerine bir Goonesne (yalnızca yeniden adlandırılmış bir kopyası Foo) kullanın (örneğin, yerine kullanın ) ve kaç ve hangi kurucuların çağrıldığını görün. (Spoiler: bir etkisi var ama çok dramatik değil.)intunordered_mapunordered_map<Foo, Goo>unordered_map<Foo, int>Goo


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.