Std से संसाधनों की चोरी :: मानचित्र की कुंजी की अनुमति है?


15

C ++ में, क्या संसाधनों को किसी नक्शे से चोरी करना ठीक है जिसे मुझे बाद में ज़रूरत नहीं है? दरअसल, मैं एक है मान std::mapके साथ std::stringकुंजी और मैं एक वेक्टर के संसाधनों चोरी से इसे से बाहर का निर्माण करना चाहते हैं mapका उपयोग कर रों चाबियाँ std::move। ध्यान दें कि इस तरह की लिखने की पहुंच आंतरिक डेटास्ट्रक्चर (कुंजियों का क्रम) को दूषित करती है, mapलेकिन मैं बाद में इसका उपयोग नहीं करूंगा।

प्रश्न : क्या मैं इसे बिना किसी समस्या के कर सकता हूं या क्या इससे उदाहरण के लिए अप्रत्याशित कीड़े पैदा हो जाएंगे mapक्योंकि मैंने इसे इस तरह से एक्सेस किया है जो std::mapइसका उद्देश्य नहीं था?

यहाँ एक उदाहरण कार्यक्रम है:

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

यह उत्पादन का उत्पादन करता है:

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

संपादित करें: मैं एक बड़े मानचित्र की संपूर्ण सामग्री के लिए यह करना चाहूंगा और इस संसाधन चोरी से गतिशील आवंटन को बचा सकता हूं।


3
इस "चोरी" का उद्देश्य क्या है? तत्व को मानचित्र से हटाने के लिए? तो फिर ऐसा क्यों न करें (तत्व को नक्शे से मिटा दें)? इसके अलावा, एक constमूल्य को संशोधित करना हमेशा यूबी होता है।
कुछ प्रोग्रामर

जाहिरा तौर पर यह गंभीर कीड़े को जन्म देगा!
rezaebrh

1
आपके प्रश्न का सीधा उत्तर नहीं है, लेकिन: क्या होगा यदि आप एक वेक्टर नहीं बल्कि एक श्रेणी या पुनरावृत्तियों की एक जोड़ी लौटाते हैं? कि पूरी तरह से नकल से बचना होगा। किसी भी स्थिति में, हॉटस्पॉट खोजने के लिए आपको ऑप्टिमाइज़ेशन की प्रगति और एक प्रोफाइलर के ट्रैक करने के लिए बेंचमार्क चाहिए।
उलरिच एकहार्ट

1
@ ALX23z क्या आपके पास इस कथन के लिए कोई स्रोत है। मैं कल्पना नहीं कर सकता कि कैसे एक पॉइंटर की नकल करना मेमोरी के पूरे क्षेत्र की नकल करने की तुलना में अधिक महंगा है।
सेबस्टियन हॉफमैन

1
@ एसबेस्टियनहॉफ़मैन इसका उल्लेख हाल ही में CppCon पर किया गया था, जिस पर निश्चित रूप से बात नहीं की गई थी। बात std::stringशॉर्ट-स्ट्रिंग अनुकूलन की है। मतलब यह है कि नकल करने और आगे बढ़ने पर कुछ गैर-तुच्छ तर्क हैं और न केवल सूचक विनिमय और इसके अलावा अधिकांश समय का अर्थ है नकल करना - ऐसा न हो कि आप लंबे समय तक तार के साथ व्यवहार करते हैं। सांख्यिकीय अंतर वैसे भी छोटा था और सामान्य तौर पर यह निश्चित रूप से निर्भर करता है कि किस प्रकार का स्ट्रिंग प्रसंस्करण किया जाता है।
ALX23z

जवाबों:


18

const_castएक constचर को संशोधित करने के लिए , आप अपरिभाषित व्यवहार कर रहे हैं । ऐसा मत करो। इसका कारण यह constहै क्योंकि नक्शे उनकी कुंजियों द्वारा क्रमबद्ध हैं। तो एक प्रमुख स्थान को संशोधित करना अंतर्निहित धारणा को तोड़ रहा है जो मानचित्र पर बनाया गया है।

आपको चर से const_castहटाने और उस चर को संशोधित करने के लिए कभी भी उपयोग नहीं करना चाहिए ।const

यही कारण है कि किया जा रहा है ने कहा, सी ++ 17 आपकी समस्या के लिए समाधान है: std::mapके extractसमारोह:

#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())));
}

और नहीं using namespace std;। :)


धन्यवाद, मैं यह कोशिश करूँगा! लेकिन क्या आप सुनिश्चित हैं कि जैसा मैंने किया, वैसा मैं नहीं कर सकता? मेरा मतलब है कि mapअगर मैं इसके तरीकों (जो मैं नहीं करता हूं) को कॉल नहीं करूंगा और हो सकता है कि इसके विध्वंसक में आंतरिक आदेश महत्वपूर्ण न हो?
फिन्ज

2
नक्शे की चाबियां बनाई जाती हैं const। एक constवस्तु को म्यूट करना तत्काल यूबी है, चाहे कुछ भी वास्तव में उन्हें बाद में एक्सेस करता है या नहीं।
HTNW

इस विधि में दो समस्याएं हैं: (1) जैसा कि मैं उन सभी तत्वों को निकालना चाहता हूं जिन्हें मैं कुंजी (अक्षम लुक) से निकालना चाहता हूं लेकिन पुनरावृत्त द्वारा। मैंने देखा है कि यह भी संभव है, इसलिए यह ठीक है। (२) अगर मैं गलत हूं तो मुझे सुधारें लेकिन सभी तत्वों को निकालने के लिए एक बहुत बड़ा ओवरहेड होगा (प्रत्येक निष्कर्षण में आंतरिक पेड़ की संरचना को पुन: संतुलन के लिए)?
फिन्ज

2
@phinz जैसा कि आप पुनरावृत्तियों पर देख सकते हैं extractजब पुनरावृत्तियों का उपयोग करते हुए तर्क ने निरंतर जटिलता को संशोधित किया है। कुछ ओवरहेड अपरिहार्य है, लेकिन यह संभवतः महत्वपूर्ण नहीं होगा। यदि आपके पास इसकी विशेष आवश्यकताएं हैं, तो इसे कवर नहीं किया जाता है, तो आपको mapइन आवश्यकताओं को पूरा करने की आवश्यकता होगी। stdकंटेनर आम सामान्य प्रयोजन application.They के लिए हैं विशिष्ट उपयोग मामलों के लिए अनुकूलित नहीं कर रहे हैं।
अखरोट

@HTNW क्या आप सुनिश्चित हैं कि चाबियाँ बनाई गई हैं const? इस मामले में आप कृपया बता सकते हैं कि मेरा तर्क कहां गलत है।
phinz

4

आपका कोड constवस्तुओं को संशोधित करने का प्रयास करता है , इसलिए इसका अपरिभाषित व्यवहार होता है, क्योंकि ड्रकरमैन का जवाब सही ढंग से इंगित करता है।

कुछ अन्य जवाब ( फ़िनज़ और ड्यूची के ) का तर्क है कि कुंजी को constऑब्जेक्ट के रूप में संग्रहीत नहीं किया जाना चाहिए क्योंकि नोड हैंडल को नक्शे से बाहर नोड्स निकालने के परिणामस्वरूप होता constहै जो कुंजी को गैर- पहुंच की अनुमति देता है । यह अनुमान पहली बार में प्रशंसनीय लग सकता है, लेकिन P0083R3 , जिस पेपर ने extractकार्य को शुरू किया है), इस विषय पर एक समर्पित अनुभाग है जो इस तर्क को अमान्य करता है:

चिंताओं

इस डिजाइन को लेकर कई चिंताएं जताई गई हैं। हम उन्हें यहां संबोधित करेंगे।

अपरिभाषित व्यवहार

सैद्धांतिक दृष्टिकोण से इस प्रस्ताव का सबसे कठिन हिस्सा यह तथ्य है कि निकाले गए तत्व अपने कास्ट कुंजी प्रकार को बरकरार रखता है। यह इसे बाहर निकलने या इसे बदलने से रोकता है। इसे हल करने के लिए, हमने कुंजी एक्सेसर फ़ंक्शन प्रदान किया है , जो नोड हैंडल द्वारा आयोजित तत्व में कुंजी को गैर-कॉन्स्टेबल एक्सेस प्रदान करता है। इस फ़ंक्शन को यह सुनिश्चित करने के लिए कार्यान्वयन "जादू" की आवश्यकता है कि यह कंपाइलर अनुकूलन की उपस्थिति में सही ढंग से काम करता है। ऐसा करने का एक तरीका संघ pair<const key_type, mapped_type> और है pair<key_type, mapped_type>। इन दोनों के बीच रूपांतरण std::launderको निष्कर्षण और पुनर्संरचना द्वारा उपयोग की जाने वाली तकनीक के समान सुरक्षित रूप से प्रभावित किया जा सकता है ।

हमें नहीं लगता है कि इससे कोई तकनीकी या दार्शनिक समस्या है। कारणों स्टैंडर्ड लाइब्रेरी मौजूद है में से एक गैर-पोर्टेबल और जादुई कोड है कि ग्राहक पोर्टेबल C ++ (जैसे नहीं लिख सकते हैं लिखने के लिए है <atomic>, <typeinfo>, <type_traits>, आदि)। यह इस तरह का एक और उदाहरण है। इस जादू को लागू करने के लिए संकलक विक्रेताओं की आवश्यकता है कि वे अनुकूलन उद्देश्यों के लिए यूनियनों में अपरिभाषित व्यवहार का दोहन नहीं करते हैं - और वर्तमान में संकलक पहले से ही इस बात का वादा करते हैं (इस सीमा तक कि यहां इसका फायदा उठाया जा रहा है)।

यह क्लाइंट पर एक प्रतिबंध लगाता है कि, यदि इन कार्यों का उपयोग किया जाता है, तो std::pairऐसे विशेष नहीं किए जा सकते हैं, जिनकी pair<const key_type, mapped_type>तुलना में एक अलग लेआउट है pair<key_type, mapped_type>। हमें लगता है कि वास्तव में ऐसा करने के इच्छुक किसी व्यक्ति की संभावना शून्य है, और औपचारिक शब्दों में हम इन जोड़ियों के किसी भी विशेषीकरण को प्रतिबंधित करते हैं।

ध्यान दें कि प्रमुख सदस्य फ़ंक्शन एकमात्र स्थान है जहां इस तरह के ट्रिक्स आवश्यक हैं, और यह कि कंटेनरों या जोड़ी में किसी भी बदलाव की आवश्यकता नहीं है।

(जोर मेरा)


यह वास्तव में मूल प्रश्न के उत्तर का एक हिस्सा है लेकिन मैं केवल एक को स्वीकार कर सकता हूं।
phinz

0

मुझे नहीं लगता है कि const_castऔर संशोधन इस मामले में अपरिभाषित व्यवहार की ओर ले जाता है लेकिन कृपया टिप्पणी करें कि क्या यह तर्क सही है।

यह उत्तर दावा करता है कि

दूसरे शब्दों में, आप यूबी प्राप्त करते हैं यदि आप एक मूल रूप से कास्ट ऑब्जेक्ट को संशोधित करते हैं, और अन्यथा नहीं।

तो v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));प्रश्न में लाइन यूबी की ओर नहीं जाती है यदि और केवल अगर stringऑब्जेक्ट p.firstको ऑब्जेक्ट ऑब्जेक्ट के रूप में नहीं बनाया गया है। अब ध्यान दें कि राज्यों के बारेextract में संदर्भ

नोड निकालने से पुनरावृत्त तत्व को पुनरावृत्तियों को अमान्य करता है। निकाले गए तत्व के संकेत और संदर्भ वैध रहते हैं, लेकिन इसका उपयोग तब नहीं किया जा सकता है जब तत्व को नोड हैंडल के स्वामित्व में रखा जाता है: यदि तत्व कंटेनर में डाला जाता है तो वे उपयोग करने योग्य हो जाते हैं।

तो अगर मैं करने के लिए इसी , इसके भंडारण स्थान पर रहने के लिए जारी है। लेकिन निष्कर्षण के बाद, मुझे druckermanly के उत्तर के कोड में संसाधनों को दूर करने की अनुमति है । इसका मतलब है कि और इसलिए भी वस्तु किया गया था नहीं स्थिरांक वस्तु के रूप में मूल रूप से बनाया।extractnode_handleppmoveppstringp.first

इसलिए, मुझे लगता है कि यू कीmap चाबियों के संशोधन से यूबी नहीं होता है और ड्यूची के उत्तर से , ऐसा लगता है कि अब दूषित पेड़ की संरचना (अब कई समान खाली स्ट्रिंग कुंजी) mapविध्वंसक में समस्याओं का परिचय नहीं देती है। तो प्रश्न में कोड कम से कम C ++ 17 में ठीक से काम करना चाहिए जहां extractविधि मौजूद है (और संकेत शेष वैध होल्ड के बारे में बयान)।

अपडेट करें

अब मैं देख रहा हूं कि यह उत्तर गलत है। मैं इसे हटा नहीं रहा हूं क्योंकि यह अन्य उत्तरों द्वारा संदर्भित है।


1
क्षमा करें, फ़िनज़, आपका उत्तर गलत है। मैं इसे समझाने के लिए एक उत्तर लिखूंगा - इसका यूनियनों और के साथ कुछ करना है std::launder
LF

-1

EDIT: यह उत्तर गलत है। तरह-तरह की टिप्पणियों ने गलतियों को इंगित किया है, लेकिन मैं इसे हटा नहीं रहा हूं क्योंकि इसे अन्य उत्तरों में संदर्भित किया गया है।

@ ड्रंकमैन ने आपके पहले प्रश्न का उत्तर दिया, जिसमें कहा गया था कि mapबल में कुंजियों को बदलने से उस क्रम को तोड़ दिया जाता है जिस पर mapआंतरिक डेटा संरचना (रेड-ब्लैक ट्री) बनी है। लेकिन extractविधि का उपयोग करना सुरक्षित है क्योंकि यह दो काम करता है: कुंजी को नक्शे से बाहर ले जाएं और फिर इसे हटा दें, इसलिए यह मानचित्र की क्रमबद्धता को बिल्कुल भी प्रभावित नहीं करता है।

दूसरा प्रश्न, जिसे आपने समझा है कि क्या यह समस्या है जब डिकंस्ट्रक्टिंग होगी, तो कोई समस्या नहीं है। जब कोई नक्शा विघटित होता है, तो वह अपने प्रत्येक तत्व (मैप्ड_टाइप्स) के डिकंस्ट्रक्टर को कॉल करेगा, और moveविधि यह सुनिश्चित करती है कि एक वर्ग को स्थानांतरित करने के बाद इसे फिर से बनाना सुरक्षित है। तो चिंता मत करो। संक्षेप में, यह उस का संचालन है moveजो यह सुनिश्चित करता है कि "स्थानांतरित" वर्ग के लिए कुछ नए मूल्य को हटाना या फिर से असाइन करना सुरक्षित है। विशेष रूप से string, moveविधि इसके चार पॉइंटर को सेट कर सकती है nullptr, इसलिए यह वास्तविक डेटा को नहीं हटाता है जो मूल वर्ग के डिकंस्ट्रक्टर को कॉल करने पर ले जाया गया था।


एक टिप्पणी ने मुझे उस बिंदु की याद दिला दी जिसे मैं देख रहा था, मूल रूप से वह सही था लेकिन एक बात है जो मैं पूरी तरह से सहमत नहीं हूं: const_castशायद एक यूबी नहीं है। constसंकलक और हमारे बीच एक वादा है। बाइनरी रूप में उनके प्रकार और अभ्यावेदन के संदर्भ में const, अभी भी एक वस्तु के रूप में उल्लेखित वस्तुएं , समान नहीं हैं const। जब constडाली जाती है, तो उसे ऐसा व्यवहार करना चाहिए मानो वह एक सामान्य उत्परिवर्ती वर्ग है। के संबंध में move, यदि आप इसका उपयोग करना चाहते हैं, तो आपको &इसके बजाय पास करना होगा const &, इसलिए जैसा कि मैं देख रहा हूं कि यह यूबी नहीं है, यह केवल constडेटा के वादे को तोड़ता है और दूर ले जाता है।

मैंने क्रमशः MSVC 14.24.28314 और Clang 9.0.0 का उपयोग करते हुए दो प्रयोग किए, और उन्होंने एक ही परिणाम प्राप्त किया।

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

उत्पादन:

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

हम देख सकते हैं कि स्ट्रिंग '2' अभी खाली है, लेकिन जाहिर है कि हमने नक्शे की क्रमबद्धता को तोड़ दिया है क्योंकि खाली स्ट्रिंग को फिर से सामने की ओर रखना चाहिए। यदि हम मानचित्र के कुछ विशिष्ट नोड्स को सम्मिलित करने, खोजने या हटाने का प्रयास करते हैं, तो इससे तबाही हो सकती है।

वैसे भी, हम इस बात पर सहमत हो सकते हैं कि किसी भी वर्ग के आंतरिक डेटा को उनके सार्वजनिक इंटरफेस को दरकिनार करना एक अच्छा विचार नहीं है। find, insert, removeकार्यों और इसके आगे आंतरिक डेटा संरचना की सुव्यवस्था पर उनकी शुद्धता भरोसा करते हैं, और उस कारण है कि हम अंदर देखना के बारे में सोचा से दूर रहना चाहिए है।


2
"के रूप में क्या यह परेशानी का कारण होगा जब deconstructing, एक समस्या नहीं है।" तकनीकी रूप से सही है, क्योंकि अपरिभाषित व्यवहार (एक constमूल्य को बदलना ) पहले हुआ था। हालाँकि, तर्क " move[ the function] यह सुनिश्चित करता है कि इसे हटाने के लिए [एक वस्तु का] एक वर्ग को सुरक्षित रखना सुरक्षित है" इसे होल्ड नहीं किया जाता है: आप किसी constवस्तु / संदर्भ से सुरक्षित रूप से स्थानांतरित नहीं हो सकते , क्योंकि इसके लिए संशोधन की आवश्यकता होती है, जिसे constरोकता है। आप का उपयोग करके उस सीमा के आसपास काम करने की कोशिश कर सकते हैं const_cast, लेकिन उस बिंदु पर आप सर्वोत्तम कार्यान्वयन विशिष्ट व्यवहार में जा रहे हैं, यदि यूबी नहीं।
हॉफमैले

@hoffmale धन्यवाद, मैंने अनदेखी की और एक बड़ी गलती की। यदि यह आप नहीं थे जो इसे इंगित करते हैं, तो यहां मेरा जवाब किसी और को गुमराह कर सकता है। वास्तव में मुझे यह कहना चाहिए कि moveफ़ंक्शन &इसके बजाय लेता है const&, इसलिए यदि कोई जोर देता है कि वह एक नक्शे से एक कुंजी बाहर ले जाता है, तो उसे उपयोग करना चाहिए const_cast
देउची

1
"कास्ट के रूप में नोट की जाने वाली वस्तुएं अभी भी एक वस्तु हैं, जो कि कॉन्स्ट के साथ नहीं हैं, उनके प्रकार और बाइनरी फॉर्म में अभ्यावेदन के संदर्भ में" नहीं। कॉस्ट ऑब्जेक्ट को केवल मेमोरी में पढ़ा जा सकता है। साथ ही, कॉन्सलर कंपाइलर को कोड के बारे में तर्क करने में सक्षम बनाता है और कई रीड्स के लिए कोड जेनरेट करने के बजाय वैल्यू को कैश करता है (जो कि प्रदर्शन के मामले में बड़ा बदलाव ला सकता है)। तो यूबी के कारण const_castहोने वाला अप्रिय है। यह अधिकांश समय काम कर सकता है, लेकिन अपने कोड को सूक्ष्म तरीके से तोड़ सकता है।
LF

लेकिन यहां मुझे लगता है कि हम यह सुनिश्चित कर सकते हैं कि वस्तुओं को केवल मेमोरी में नहीं रखा जाए क्योंकि बाद में extract, हमें उसी ऑब्जेक्ट से स्थानांतरित करने की अनुमति है, है ना? (मेरा उत्तर देखें)
phinz

1
क्षमा करें, आपके उत्तर में विश्लेषण गलत है। मैं इसे समझाने के लिए एक उत्तर लिखूंगा - इसका यूनियनों के साथ कुछ करना है औरstd::launder
LF
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.