Kaynaklardan std :: map anahtarlarının çalınmasına izin var mı?


15

C ++ 'da, artık ihtiyaç duymadığım bir haritadan kaynakları çalmak sorun değil mi? Daha doğrusu, ben bir olduğunu varsayalım std::mapile std::stringanahtar ve ben kaynaklarını çalarak bunun bir vektör dışarı inşa etmek istiyorum mapkullanarak s tuşları std::move. Anahtarlara böyle bir yazma erişiminin iç veri yapısını (anahtarların sıralaması) bozduğunu mapancak daha sonra kullanmayacağımı unutmayın.

Soru : Bunu herhangi bir sorun olmadan yapabilir miyim yoksa beklenmedik bir hataya yol açacak mapmıyım std::map?

İşte bir örnek program:

#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
    std::vector<std::pair<std::string,double>> v;
    { // new scope to make clear that m is not needed 
      // after the resources were stolen
        std::map<std::string,double> m;
        m["aLongString"]=1.0;
        m["anotherLongString"]=2.0;
        //
        // now steal resources
        for (auto &p : m) {
            // according to my IDE, p has type 
            // std::pair<const class std::__cxx11::basic_string<char>, double>&
            cout<<"key before stealing: "<<p.first<<endl;
            v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
            cout<<"key after stealing: "<<p.first<<endl;
        }
    }
    // now use v
    return 0;
}

Çıktı üretir:

key before stealing: aLongString
key after stealing: 
key before stealing: anotherLongString
key after stealing: 

EDIT: Bunu büyük bir haritanın tüm içeriği için yapmak ve bu kaynak çalmak tarafından dinamik ayırma kaydetmek istiyorum.


3
Bu "çalmanın" amacı nedir? Öğeyi haritadan kaldırmak için? Öyleyse neden bunu yapmıyorsunuz (öğeyi haritadan silmek)? Ayrıca, bir constdeğeri değiştirmek her zaman UB'dir.
Bazı programcı dostum

Görünüşe göre ciddi hatalara yol açacak!
rezaebrh

1
Sorunuza doğrudan bir cevap değil, ama: Bir vektörü değil, bir aralığı veya bir çift yineleyiciyi döndürürseniz ne olur? Bu tamamen kopyalamayı önler. Her durumda, optimizasyonun ilerlemesini izlemek için kıstaslara ve sıcak noktaları bulmak için bir profilciye ihtiyacınız vardır.
Ulrich Eckhardt

1
@ ALX23z Bu ifade için kaynakınız var mı? Bir işaretçiyi kopyalamanın, tüm bir bellek bölgesini kopyalamaktan daha pahalı olduğunu hayal edemiyorum.
Sebastian Hoffmann

1
@SebastianHoffmann, geçen CppCon'da hangi konuşmadan emin olmadığından bahsedildi, tho. Mesele std::stringkısa dize optimizasyonudur. Bu, yalnızca işaretçi değişiminde değil, kopyalama ve taşımada önemsiz olmayan bir mantık olduğu anlamına gelir ve ayrıca çoğu zaman hareket etmek kopyalama anlamına gelir - oldukça uzun dizelerle uğraşmak için. İstatistiksel fark her halükarda küçüktü ve genel olarak, ne tür bir dize işlemenin gerçekleştirildiğine bağlı olarak kesinlikle değişir.
ALX23z

Yanıtlar:


18

const_castBir constdeğişkeni değiştirmek için kullanarak tanımsız davranış yapıyorsunuz . Bunu yapma. Bunun nedeni const, haritaların anahtarlarına göre sıralanmasıdır. Bu nedenle, bir anahtar yerinde değişiklik yapmak haritanın üzerine inşa edildiği varsayımını kırıyor.

Bir değişkeni const_castkaldırmak ve bu değişkeni değiştirmek için hiçbir zaman kullanmamalısınız .const

Bu varlık C ++ 17 sorununuza çözüm vardır, şöyle konuştu: std::map'nin extractfonksiyonu:

#include <map>
#include <string>
#include <vector>
#include <utility>

int main() {
  std::vector<std::pair<std::string, double>> v;
  std::map<std::string, double> m{{"aLongString", 1.0},
                                  {"anotherLongString", 2.0}};

  auto extracted_value = m.extract("aLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));

  extracted_value = m.extract("anotherLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));
}

Ve yapma using namespace std;. :)


Teşekkürler, bunu deneyeceğim! Ama yaptığım gibi yapamayacağımdan emin misin? Demek istediğim, mapyöntemlerini çağırmazsam şikayet etmeyeceğim (ki ben yapmıyorum) ve belki de iç düzen yıkıcısında önemli değil mi?
phinz

2
Haritanın tuşları oluşturulur const. Bir constnesneyi mutasyona uğratmak , daha sonra herhangi bir şeye erişip erişmediği anında UB'dir .
HTNW

Bu yöntemin iki sorunu vardır: (1) Tüm öğeleri ayıklamak istediğim için anahtar (verimsiz arama) değil yineleyici ile ayıklamak istemiyorum. Bunun da mümkün olduğunu gördüm, bu yüzden bu iyi. (2) Yanılıyorsam beni düzeltin ama tüm unsurları çıkarmak için muazzam bir yük olacak (her bir ekstraksiyonda iç ağaç yapısını yeniden dengelemek için)?
phinz

2
@phinz Yineleyicileri argüman olarak kullanırken cppreference üzerinde görebileceğiniz extract gibi sürekli karmaşıklığı amorti etti. Bazı yükler kaçınılmazdır, ancak muhtemelen önemli olacak kadar önemli olmayacaktır. Bunun kapsamına girmeyen özel gereksinimleriniz varsa, mapbu gereksinimleri karşılayan kendi gereksinimlerinizi uygulamanız gerekir . stdKonteynerler ortak genel amaçlı application.They içindir özel kullanım durumları için optimize edilmemiştir.
ceviz

@HTNW Anahtarların oluşturulduğundan emin misiniz const? Bu durumda lütfen argümanlarımın nerede yanlış olduğunu belirtebilirsiniz .
phinz

4

Kodunuz constnesneleri değiştirmeye çalışır , bu nedenle druckermanly'nin cevabı doğru bir şekilde işaret ettiği için tanımlanmamış bir davranışa sahiptir .

Diğer bazı cevaplar ( phinz ve Deuchie ) anahtarın constnesne olarak saklanmaması gerektiğini savunur, çünkü düğüm tanıtıcısı haritadan düğüm çıkartılmasından kaynaklanmıştır const, anahtarın erişime izin vermez . Bu çıkarım ilk başta akla yatkın görünebilir, ancak P0083R3 , extractişlevleri tanıtan makale ), bu konuda bu argümanı geçersiz kılan özel bir bölüme sahiptir:

Endişeler

Bu tasarım hakkında birçok endişe dile getirildi. Bunları burada ele alacağız.

Tanımsız davranış

Bu teklifin teorik bir bakış açısından en zor kısmı, çıkarılan öğenin sabit anahtar türünü korumasıdır. Bu, hareket etmesini veya değiştirilmesini önler. Bunu çözmek için , düğüm tanıtıcısı tarafından tutulan öğedeki anahtara erişimsiz erişim sağlayan anahtar erişimci işlevini sağladık . Bu işlev, derleyici optimizasyonlarının varlığında doğru çalıştığından emin olmak için uygulama "sihirli" gerektirir. Bunu yapmanın bir yolu birliği ile pair<const key_type, mapped_type> ve pair<key_type, mapped_type>. Bunlar arasındaki dönüşüm std::launder, ekstraksiyon ve yeniden yerleştirmede kullanılana benzer bir teknik kullanılarak güvenli bir şekilde gerçekleştirilebilir .

Bunun herhangi bir teknik veya felsefi sorun yarattığını düşünmüyoruz. Standart Kütüphanesi var nedenlerinden biri istemci ++ (örneğin taşınabilir C yazamaz dışı taşınabilir ve büyülü kod yazmaktır <atomic>, <typeinfo>, <type_traits>vb.) Bu sadece başka bir örnek. Derleyici satıcılarının bu büyüyü uygulaması için gerekli olan tek şey, optimizasyon amacıyla sendikalarda tanımlanmamış davranıştan faydalanmamalarıdır ve şu anda derleyiciler bunu vaat etmektedir (buradan faydalandığı ölçüde).

Bu, istemciye, bu işlevler kullanılırsa, std::pairdaha pair<const key_type, mapped_type>farklı bir düzene sahip olacak şekilde özelleştirilemeyecek bir kısıtlama getirir pair<key_type, mapped_type>. Bunu yapmak isteyen herkesin etkili bir şekilde sıfır olduğunu düşünüyoruz ve resmi ifadede bu çiftlerin herhangi bir uzmanlaşmasını kısıtlıyoruz.

O Not anahtar üye fonksiyona hileler gerekli olan tek yerdir ve kaplara veya çiftine hiçbir değişiklik gerekli olduğunu.

(benimkini vurgula)


Bu aslında asıl sorunun cevabının bir parçası ama sadece birini kabul edebiliyorum.
phinz

0

Ben const_castve değişiklik bu durumda tanımsız davranışa yol açtığını sanmıyorum ama lütfen bu argümanın doğru olup olmadığını yorumlayın.

Bu cevap iddia ediyor

Başka bir deyişle, orijinal bir const nesnesini değiştirirseniz, aksi takdirde değiştirmezseniz UB elde edersiniz.

Bu nedenle, v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));sorudaki çizgi , yalnızca stringnesne p.firstbir const nesnesi olarak oluşturulmamışsa UB'ye yol açmaz . Şimdi eyaletlerle ilgili referansınextract

Bir düğümün ayıklanması, ayıklanan öğenin yineleyicilerini geçersiz kılar. Çıkarılan öğeye yönelik işaretçiler ve referanslar geçerli kalır, ancak öğenin bir düğüm tutamacına sahip olması durumunda kullanılamaz: öğe bir kaba yerleştirilirse kullanılabilir hale gelirler.

Ben Yani tekabül , depolama konumunda yaşamaya devam ediyor. Ancak çıkarıldıktan sonra , druckermanly'nin cevabının kodundaki gibi kaynakları uzak tutmama izin verildi . Bu demektir ve bu nedenle, aynı zamanda nesne edilmiş olup başlangıçta const nesne olarak oluşturulur.extractnode_handleppmoveppstringp.first

Bu nedenle, map's tuşları değişiklik UB yol açmaz ve Deuchie cevabından düşünüyorum, aynı zamanda şimdi bozuk ağaç yapısı (şimdi birden çok boş boş dize anahtarları) mapyıkıcı sorunları getirmez gibi görünüyor . Bu nedenle, sorudaki kod, en azından extractyöntemin bulunduğu C ++ 17'de iyi çalışmalıdır (ve geçerli kalan işaretçiler hakkındaki ifade).

Güncelleme

Şimdi bu cevabın yanlış olduğu görüşündeyim. Diğer cevaplar tarafından atıfta bulunduğundan onu silmiyorum.


1
Üzgünüm, phinz, cevabın yanlış. Bunu açıklamak için bir cevap yazacağım - bu sendikalar ile bir ilgisi vardır std::launder.
LF

-1

EDIT: Bu cevap yanlış. Nazik yorumlar hataları işaret etti, ancak diğer cevaplarda atıfta bulunulduğu için silmiyorum.

@druckermanly, anahtarlarınızı mapzorla değiştirmenin mapdahili veri yapısının inşa edildiği düzeni (Kırmızı-Siyah ağaç) bozduğunu söyleyen ilk sorunuza cevap verdi . Ancak extractyöntemi kullanmak güvenlidir, çünkü iki şey yapar: anahtarı haritadan çıkarın ve silin, böylece haritanın düzenini hiç etkilemez.

Yapıyı bozarken sorun yaratıp yaratmayacağı sorusundaki diğer soru sorun değil. Bir harita yapısını bozduğunda, öğelerinin her birinin (mapped_types vb.) Yapısökücüsünü çağıracaktır ve moveyöntem, taşındıktan sonra bir sınıfın yapısökümünün güvenli olmasını sağlar. Yani endişelenme. Özetle, move"taşınan" sınıfa yeni bir değer silmenin veya yeniden atamanın güvenli olmasını sağlayan işlemdir . Özellikle string, moveyöntem char işaretçisini olarak ayarlayabilir nullptr, böylece orijinal sınıfın yapısökücüsü çağrıldığında taşınan gerçek verileri silmez.


Bir yorum bana baktığım noktayı hatırlattı, temelde haklıydı ama tamamen katılmadığım bir şey var: const_castmuhtemelen bir UB değil. constderleyici ve bizim aramızda bir sözdür. olarak kaydedilen nesneler , türleri ve ikili formdaki gösterimleri bakımından, constolmayanlarla aynı olan bir nesnedir const. Ne zaman constuzak döküm normal bir değişken sınıftır sanki davranması gerekir. İle ilgili olarak move, eğer kullanmak istiyorsanız, bir &yerine geçmek zorundasınız const &, bu yüzden bir UB olmadığını gördüğüm gibi, sadece sözünü keser constve verileri uzaklaştırır.

Ayrıca sırasıyla MSVC 14.24.28314 ve Clang 9.0.0 kullanarak iki deney yaptım ve aynı sonucu verdiler.

map<string, int> m;
m.insert({ "test", 2 });
m.insert({ "this should be behind the 'test' string.", 3 });
m.insert({ "and this should be in front of the 'test' string.", 1 });
string let_me_USE_IT = std::move(const_cast<string&>(m.find("test")->first));
cout << let_me_USE_IT << '\n';
for (auto const& i : m) {
    cout << i.first << ' ' << i.second << '\n';
}

çıktı:

test
and this should be in front of the 'test' string. 1
 2
this should be behind the 'test' string. 3

'2' dizesinin artık boş olduğunu görebiliyoruz, ancak açık bir şekilde haritanın düzenini bozduk çünkü boş dize öne yerleştirilmelidir. Haritanın bazı belirli düğümlerini eklemeye, bulmaya veya silmeye çalışırsak, bu bir felakete neden olabilir.

Her neyse, herhangi bir sınıfın iç verilerini kamu arayüzlerini atlayarak manipüle etmenin asla iyi bir fikir olmadığı konusunda anlaşabiliriz. find, insert, removeVb fonksiyonlar ve iç veri yapısının düzen üzerindeki doğruluğunu itimat ve biz içini Bakmak düşüncesi uzak durmalısınız nedeni budur.


2
diyerek şöyle devam etti: "Yapısızlaştırma sırasında sorun yaratıp yaratmayacağı bir sorun değil." Teknik olarak doğrudur, çünkü tanımlanmamış davranış (bir constdeğerin değiştirilmesi ) daha önce gerçekleşmiştir. Ancak, " move[işlev] argümanı, bir sınıfın taşındıktan sonra [bir nesnesinin] yapıbozumunun güvenli olmasını sağlar: Bu, bir constnesneden / referanstan güvenli bir şekilde hareket edemezsiniz ; constönler. Kullanarak bu sınırlama üzerinde çalışabilirsiniz const_cast, ancak bu noktada en iyi UB değilse, özel uygulama davranış derinlemesine gidiyorsunuz.
hoffmale

@hoffmale Teşekkürler, gözden kaçtım ve büyük bir hata yaptım. Eğer onu işaret eden siz değilseniz, buradaki cevabım başka birini yanıltabilir. Aslında movefonksiyonun a &yerine aldığını söylemeliyim const&, bu yüzden bir anahtarın haritadan çıkarılması konusunda ısrar ederse, kullanması gerekir const_cast.
Deuchie

1
"const olarak belirtilen nesneler, türleri ve ikili biçimdeki gösterimleri bakımından const ile olmayanlarla aynı olan bir nesnedir" No. const nesneleri salt okunur belleğe yerleştirilebilir. Ayrıca const, derleyicinin kod hakkında akıl yürütmesini ve birden fazla okuma için kod oluşturmak yerine değeri önbelleğe almasını sağlar (bu, performans açısından büyük bir fark yaratabilir). Böylece UB'nin neden const_castolduğu iğrenç olacak. Çoğu zaman işe yarayabilir, ancak kodunuzu ince yollarla kırın.
LF

Ama burada, nesnelerin salt okunur belleğe alınmadığından emin olabileceğimizi düşünüyorum, çünkü daha sonra extractaynı nesneden hareket etmemize izin veriliyor, değil mi? (cevabımı gör)
phinz

1
Üzgünüz, cevabınızdaki analiz yanlış. Bunu açıklamak için bir cevap yazacağım - sendikalarla ilgisi var vestd::launder
LF
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.