C ++ standartlar komitesi, C ++ 11'de unordered_map'in eklediklerini yok etmesini mi amaçlıyor?


114

Unordered_map :: insert () eklediğiniz değişkeni yok ettiği çok garip bir hatayı izleyerek hayatımın üç gününü kaybettim. Bu oldukça açık olmayan davranış yalnızca çok yeni derleyicilerde görülür: clang 3.2-3.4 ve GCC 4.8'in bu "özelliği" gösteren tek derleyiciler olduğunu buldum .

İşte ana kod tabanımdan sorunu gösteren bazı azaltılmış kodlar:

#include <memory>
#include <unordered_map>
#include <iostream>

int main(void)
{
  std::unordered_map<int, std::shared_ptr<int>> map;
  auto a(std::make_pair(5, std::make_shared<int>(5)));
  std::cout << "a.second is " << a.second.get() << std::endl;
  map.insert(a); // Note we are NOT doing insert(std::move(a))
  std::cout << "a.second is now " << a.second.get() << std::endl;
  return 0;
}

Muhtemelen çoğu C ++ programcısı gibi, çıktının şöyle görünmesini beklerdim:

a.second is 0x8c14048
a.second is now 0x8c14048

Ancak clang 3.2-3.4 ve GCC 4.8 ile bunun yerine şunu alıyorum:

a.second is 0xe03088
a.second is now 0

Http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ adresinde unordered_map :: insert () belgelerini yakından inceleyene kadar bu hiç mantıklı gelmeyebilir, burada 2 numaralı aşırı yük:

template <class P> pair<iterator,bool> insert ( P&& val );

Hangi açgözlü bir evrensel referans hareket aşırı yük olan bir şey diğer aşırı herhangi eşleşen değil, tüketen inşa taşımak bir VALUE_TYPE içine. Öyleyse neden yukarıdaki kodumuz bu aşırı yüklemeyi seçti ve muhtemelen çoğu kişinin beklediği gibi unordered_map :: value_type aşırı yüklemesini seçmedi?

Cevap yüzünüze bakıyor: unordered_map :: value_type bir çifttir < const int, std :: shared_ptr> ve derleyici doğru bir şekilde < int , std :: shared_ptr> çiftinin dönüştürülebilir olmadığını düşünecektir . Dolayısıyla derleyici hareket evrensel referans aşırı seçer ve o, orijinali yok eder rağmen bir değişken tahrip alma ile Tamam göstermek için tipik kongre olan std :: hamle () kullanmayan programcı. Bu nedenle, eklenti yok etme davranışı aslında C ++ 11 standardına göre doğrudur ve daha eski derleyiciler yanlıştır .

Bu hatayı teşhis etmek için neden üç günümü aldığımı muhtemelen şimdi anlayabilirsiniz. Unordered_map'e eklenen türün, kaynak kodu terimlerinde çok uzakta tanımlanmış bir typedef olduğu büyük bir kod tabanında hiç de açık değildi ve typedef'in değer_type ile aynı olup olmadığını kontrol etmek hiç kimsenin aklına gelmedi.

Yani Stack Overflow'a sorularım:

  1. Eski derleyiciler neden daha yeni derleyiciler gibi eklenen değişkenleri yok etmez? Demek istediğim, GCC 4.7 bile bunu yapmıyor ve oldukça standartlara uyuyor.

  2. Bu sorun yaygın olarak biliniyor mu, çünkü kesinlikle derleyicileri yükseltmek, eskiden çalışan kodun aniden çalışmayı durdurmasına neden olacak mı?

  3. C ++ standartları komitesi bu davranışı amaçladı mı?

  4. Daha iyi davranış sağlamak için unordered_map :: insert () değiştirilmesini nasıl önerirsiniz? Bunu soruyorum çünkü burada destek varsa, bu davranışı WG21'e bir N notu olarak göndermeyi ve onlardan daha iyi bir davranış uygulamasını istemeyi düşünüyorum.


10
Evrensel bir ref kullanması, eklenen değerin her zaman taşındığı anlamına gelmez - bunu yalnızca rdeğerler için yapmalıdır , ki düz adeğildir. Bir kopya yapmalı. Ayrıca, bu davranış derleyiciye değil tamamen stdlib'e bağlıdır.
Xeo

10
Bu, kütüphanenin uygulanmasında bir hata gibi görünüyor
David Rodríguez - dribeas

4
"Bu nedenle, eklenti yok etme davranışı aslında C ++ 11 standardına göre doğrudur ve daha eski derleyiciler yanlıştır." Üzgünüm ama yanılıyorsun. Bu fikri C ++ Standardının hangi kısmından aldınız? BTW cplusplus.com resmi değil.
Ben Voigt

1
Bunu sistemimde yeniden üretemiyorum ve 4.9.0 20131223 (experimental)sırasıyla gcc 4.8.2 kullanıyorum . Çıktı benim için a.second is now 0x2074088 (veya benzeri).

47
Bu, 2013-06'da 4.8.2 için düzeltilen 4.8 serisindeki bir gerileme olan GCC hatası 57619'du.
Casey

Yanıtlar:


83

Diğerlerinin yorumlarda işaret ettiği gibi, "evrensel" kurucunun aslında her zaman kendi argümanından uzaklaşması beklenmez. Argüman gerçekten bir r değeri ise hareket etmesi ve bir l değeri ise kopyalaması gerekir.

Gözlemlediğiniz, her zaman hareket eden davranış, libstdc ++ 'daki bir hatadır ve artık soruya yapılan bir yoruma göre düzeltilmiştir. Merak edenler için g ++ - 4.8 başlıklarına bir göz attım.

bits/stl_map.h, 598-603. satırlar

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); }

bits/unordered_map.h, 365-370. satırlar

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_h.insert(std::move(__x)); }

İkincisi, kullanılması std::movegereken yerde yanlış kullanıyor std::forward.


11
Clang, varsayılan olarak GCC'nin stdlib'i olan libstdc ++ kullanır.
Xeo

Gcc 4.9 kullanıyorum ve bakıyorum libstdc++-v3/include/bits/. Ben aynı şeyi görmüyorum. Görüyorum { return _M_h.insert(std::forward<_Pair>(__x)); }. 4.8 için farklı olabilir, ancak henüz kontrol etmedim.

Evet, sanırım hatayı düzelttiler.
Brian

@Brian Hayır, sistem başlıklarımı kontrol ettim. Aynı şey. 4.8.2 btw.

Benimki 4.8.1, yani sanırım ikisi arasında düzeltildi.
Brian

20
template <class P> pair<iterator,bool> insert ( P&& val );

Bu, açgözlü bir evrensel referans hareket aşırı yüklemesidir, diğer aşırı yüklerden hiçbiriyle eşleşmeyen herhangi bir şeyi tüketir ve onu bir değer_tipine dönüştürür.

Bazı insanların evrensel referans dediği şey budur , ancak gerçekten referans çöküşüdür . Bağımsız değişken bir senin durumunda, lvalue Çeşidi pair<int,shared_ptr<int>>o olacak değil bir rvalue referansı olmak argüman neden ve olmamalıdır ondan hareket ettirin.

Öyleyse neden yukarıdaki kodumuz bu aşırı yüklemeyi seçti ve muhtemelen çoğu kişinin beklediği gibi unordered_map :: value_type aşırı yüklemesini seçmedi?

Çünkü siz, daha önce birçok insan gibi value_type, kabın içindekileri yanlış yorumladınız . value_typeArasında *map(sıralı veya sırasız olsun) 'dir pair<const K, T>sizin durumunuzda olan pair<const int, shared_ptr<int>>. Eşleşmeyen tür, beklediğiniz aşırı yükü ortadan kaldırır:

iterator       insert(const_iterator hint, const value_type& obj);

Hala neden bu yeni lemmanın var olduğunu anlamıyorum, "evrensel referans", gerçekten spesifik bir şey ifade etmiyor, ne de pratikte tamamen "evrensel" olmayan bir şey için iyi bir isim taşıyor. Şablonlar dilde tanıtıldığından beri olduğu gibi C ++ meta-dil davranışının bir parçası olan bazı eski daraltma kurallarını ve ayrıca C ++ 11 standardından yeni bir imzayı hatırlamak çok daha iyidir. Yine de bu evrensel referanslardan bahsetmenin faydası nedir? Zaten yeterince tuhaf isimler ve şeyler var, std::movebunun hiçbir şeyi hareket ettirmediği gibi .
user2485710

2
@ user2485710 Bazen, cehalet mutluluktur ve tüm referans daraltma ve şablon türü kesinti ayarlamalarında "evrensel referans" şemsiye terimine sahip olmak IMHO'nun oldukça mantıksız olduğu ve bu ince ayarın std::forwardgerçek işi yapması için kullanılması gerekliliğidir ... Scott Meyers yönlendirme için oldukça basit kurallar koyan (evrensel referansların kullanımı) iyi bir iş çıkarmıştır.
Mark Garcia

1
Benim düşünceme göre, "evrensel referans", hem l değerlerine hem de r değerlerine bağlanabilen işlev şablonu parametrelerini bildirmek için bir modeldir. "Referans daraltma", şablon parametrelerini tanımlara, "evrensel referans" durumlarında ve diğer bağlamlarda ikame ederken (çıkarılmış veya belirlenmiş) olan şeydir.
aschepler

2
@aschepler: evrensel referans , referans daraltmanın bir alt kümesi için sadece süslü bir isimdir. Cehaletin mutluluk olduğuna katılıyorum ve ayrıca süslü bir isme sahip olmanın onun hakkında konuşmayı daha kolay ve daha trend hale getirdiği ve bu davranışın yayılmasına yardımcı olabileceği gerçeğine katılıyorum . Bununla birlikte, ismin büyük bir hayranı değilim, çünkü gerçek kuralları bilinmesi gerekmeyen bir köşeye itiyor ... yani, Scott Meyers'ın tarif ettiği şeyin dışında bir vakaya varana kadar.
David Rodríguez - dribeas

Artık anlamsız anlambilim, ancak önermeye çalıştığım ayrım: Evrensel başvuru, bir işlev parametresini çıkarılabilir şablon parametresi olarak tasarladığımda ve bildirdiğimde gerçekleşir &&; bir derleyici bir şablon başlattığında başvuru daraltma gerçekleşir. Referans çökmesi evrensel referansların çalışmasının sebebidir, ancak beynim iki terimi aynı alana koymayı sevmiyor.
aschepler
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.