C ++ 11 में एक प्रकार की गैर-चल को कब बनाया जाए?


127

मुझे आश्चर्य हुआ कि यह मेरे खोज परिणामों में नहीं दिखा, मुझे लगा कि किसी ने पहले यह पूछा होगा, सी ++ 11 में चाल शब्दार्थ की उपयोगिता को देखते हुए:

मुझे C ++ 11 में एक कक्षा को गैर-चल बनाने योग्य बनाने के लिए कब (या यह मेरे लिए एक अच्छा विचार है) है?

( मौजूदा कोड के साथ संगतता समस्याओं के अलावा अन्य कारण , वह है)


2
बूस्ट हमेशा एक कदम आगे है - "महंगे टू मूव टाइप्स" ( boost.org/doc/libs/1_48_0/doc/html/container/move_emplace.html )
SChepurin

1
मुझे लगता है कि यह +1हर्ब (या उसके जुड़वाँ, जैसा कि ऐसा लगता है ) के बहुत गहन उत्तर के साथ एक बहुत अच्छा और उपयोगी प्रश्न है , इसलिए मैंने इसे एक FAQ प्रविष्टि बनाया। अगर कोई वस्तु मुझे बस लाउंज में पिंग करती है , तो इस पर वहां चर्चा की जा सकती है।
sbi

1
AFAIK जंगम कक्षाएं अभी भी स्लाइसिंग के अधीन हो सकती हैं, इसलिए यह सभी बहुरूपी आधार वर्गों (यानी आभासी कार्यों के साथ सभी आधार वर्गों) के लिए चलती (और नकल) करने से मना करता है।
फिलिप

1
@ मेहरदाद: मैं सिर्फ यह कह रहा हूं कि "T का मूव कंस्ट्रक्टर है" और " T x = std::move(anotherT);लीगल होना" समतुल्य नहीं है। उत्तरार्द्ध एक चाल-अनुरोध है जो प्रतिलिपि Ctor पर वापस आ सकता है यदि T में कोई चाल ctor नहीं है। तो, "चल" का क्या मतलब है?
sellibitze

1
@ मेहरदाद: "MoveConstructible" का अर्थ है C ++ मानक पुस्तकालय अनुभाग देखें। कुछ पुनरावृत्तियों में एक चाल निर्माता नहीं हो सकता है, लेकिन यह अभी भी MoveConstructible है। "चल" लोगों की अलग-अलग परिभाषाओं को ध्यान में रखकर देखें।
sellibitze

जवाबों:


110

हर्ब का जवाब (इससे पहले कि इसे संपादित किया गया था) वास्तव में एक प्रकार का एक अच्छा उदाहरण देता है जो जंगम नहीं होना चाहिएstd::mutex :।

OS का मूल म्यूटेक्स प्रकार (जैसे pthread_mutex_tPOSIX प्लेटफ़ॉर्म पर) "स्थान अपरिवर्तनीय" नहीं हो सकता है जिसका अर्थ है कि वस्तु का पता उसके मूल्य का हिस्सा है। उदाहरण के लिए, OS सभी आरंभिक म्यूटेक्स ऑब्जेक्ट की ओर संकेत कर सकता है। यदि std::mutexडेटा सदस्य के रूप में एक मूल OS म्यूटेक्स प्रकार समाहित है और मूल प्रकार का पता निश्चित रहना चाहिए (क्योंकि OS अपने म्यूटेक्स की ओर संकेत करता है) तो या तो std::mutexमूल mutex प्रकार को ढेर पर संग्रहीत करना होगा, जिस पर वह रुकेगा std::mutexवस्तुओं के बीच स्थानांतरित होने पर या std::mutexस्थानांतरित नहीं होना चाहिए। ढेर पर यह भंडारण संभव है क्योंकि एक नहीं है, std::mutexएक है constexprनिर्माता और निरंतर प्रारंभ (यानी स्थिर आरंभीकरण) तो एक वैश्विक कि लिए योग्य होना चाहिएstd::mutexकार्यक्रम के निष्पादन शुरू होने से पहले निर्माण करने की गारंटी है, इसलिए इसका निर्माणकर्ता उपयोग नहीं कर सकता है new। इसलिए बचा एकमात्र विकल्प std::mutexअचल होने के लिए है।

एक ही तर्क अन्य प्रकारों पर लागू होता है जिसमें कुछ ऐसा होता है जिसे एक निश्चित पते की आवश्यकता होती है। यदि संसाधन का पता निश्चित रहना चाहिए, तो उसे स्थानांतरित न करें!

आगे बढ़ने के लिए एक और तर्क std::mutexहै जो यह है कि इसे सुरक्षित रूप से करना बहुत कठिन होगा, क्योंकि आपको यह जानना होगा कि कोई भी उस समय म्यूटेक्स को लॉक करने की कोशिश नहीं कर रहा है जो इसे स्थानांतरित किया जा रहा है। चूंकि म्यूटेक्स बिल्डिंग ब्लॉकों में से एक है, जिसका उपयोग आप डेटा रेस को रोकने के लिए कर सकते हैं, यह दुर्भाग्यपूर्ण होगा यदि वे स्वयं रेस के लिए सुरक्षित नहीं थे! अचल होने के साथ std::mutexही आप जानते हैं कि एक बार निर्माण होने के बाद ही कोई भी ऐसा कर सकता है और नष्ट होने से पहले इसे लॉक करना और इसे अनलॉक करना है, और उन ऑपरेशनों को स्पष्ट रूप से थ्रेड सुरक्षित होने और डेटा दौड़ शुरू नहीं करने की गारंटी है। यही तर्क std::atomic<T>वस्तुओं पर लागू होता है : जब तक कि उन्हें परमाणु रूप से स्थानांतरित नहीं किया जा सकता है तब तक उन्हें सुरक्षित रूप से स्थानांतरित करना संभव नहीं होगा, एक और धागा कॉल करने की कोशिश कर सकता हैcompare_exchange_strongइस समय वस्तु को दाईं ओर ले जाया जा रहा है। तो एक और मामला जहां प्रकार नहीं होने चाहिए, वह वह है जहां वे सुरक्षित समवर्ती कोड के निम्न-स्तर के निर्माण खंड हैं और उन पर सभी कार्यों की परमाणुता सुनिश्चित करनी चाहिए। यदि किसी भी समय वस्तु के मूल्य को एक नई वस्तु में ले जाया जा सकता है, तो आपको प्रत्येक परमाणु चर की रक्षा के लिए एक परमाणु चर का उपयोग करने की आवश्यकता होगी, ताकि आप जान सकें कि क्या इसका उपयोग करना सुरक्षित है या इसे स्थानांतरित कर दिया गया है ... और रक्षा करने के लिए एक परमाणु चर उस परमाणु चर, और इतने पर ...

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


17
+1 कुछ का एक कम विदेशी उदाहरण जिसे स्थानांतरित नहीं किया जा सकता क्योंकि इसमें एक विशेष पता है जो एक निर्देशित ग्राफ संरचना में एक नोड है।
पोटाटोस्वाटर

3
यदि म्यूटेक्स गैर-प्रतिलिपि योग्य और गैर-चल योग्य है, तो मैं उस ऑब्जेक्ट को कैसे कॉपी या स्थानांतरित कर सकता हूं जिसमें एक म्यूटेक्स शामिल है? (तुल्यकालन के लिए खुद के साथ एक थ्रेड सेफ क्लास की तरह ...)
tr3w

4
@ tr3w, आप नहीं कर सकते, जब तक कि आप ढेर पर म्यूटेक्स नहीं बनाते हैं और इसे एक अनूठे_प्ट्र या समान के माध्यम से पकड़ते हैं
जोनाथन

2
@ tr3w: क्या आप म्यूटेक्स हिस्से को छोड़कर पूरी कक्षा को स्थानांतरित नहीं करेंगे ?
user541686

3
@BenVoigt, लेकिन नई वस्तु का अपना म्यूटेक्स होगा। मुझे लगता है कि उसका अर्थ है उपयोगकर्ता-परिभाषित चाल परिचालन जो म्यूटेक्स सदस्य को छोड़कर सभी सदस्यों को स्थानांतरित करता है। तो क्या होगा अगर पुरानी वस्तु समाप्त हो रही है? इसका म्यूटेक्स इसके साथ समाप्त होता है।
जोनाथन वेकली

57

संक्षिप्त उत्तर: यदि कोई प्रकार कॉपी करने योग्य है, तो वह भी जंगम होना चाहिए। हालाँकि, रिवर्स सच नहीं है: कुछ प्रकार जैसे std::unique_ptrअभी तक चलने योग्य हैं फिर भी उन्हें कॉपी करने का कोई मतलब नहीं है; ये स्वाभाविक रूप से केवल-प्रकार के होते हैं।

थोड़ा लंबा जवाब इस प्रकार है ...

दो प्रमुख प्रकार के प्रकार हैं (अन्य विशेष-प्रयोजन वाले जैसे लक्षण):

  1. मूल्य की तरह इस तरह के रूप प्रकार, intया vector<widget>। ये मूल्यों का प्रतिनिधित्व करते हैं, और स्वाभाविक रूप से प्रतिलिपि योग्य होना चाहिए। C ++ 11 में, आम तौर पर आपको कॉपी के अनुकूलन के रूप में स्थानांतरित करने के बारे में सोचना चाहिए, और इसलिए सभी कॉपी करने योग्य प्रकार स्वाभाविक रूप से जंगम होना चाहिए ... हिलना-डुलना अक्सर-आम मामले में कॉपी करने का एक कुशल तरीका है कि आप डॉन ' t किसी भी और मूल वस्तु की आवश्यकता है और बस वैसे भी इसे नष्ट करने जा रहे हैं।

  2. संदर्भ जैसे प्रकार जो वंशानुगत पदानुक्रम में मौजूद हैं, जैसे आधार वर्ग और आभासी या संरक्षित सदस्य कार्यों के साथ कक्षाएं। ये आमतौर पर सूचक या संदर्भ द्वारा आयोजित होते हैं, अक्सर base*या base&तो, और इसलिए स्लाइसिंग से बचने के लिए प्रतिलिपि निर्माण प्रदान नहीं करते हैं; यदि आप एक मौजूदा वस्तु की तरह एक और वस्तु प्राप्त करना चाहते हैं, तो आप आमतौर पर एक आभासी फ़ंक्शन को कॉल करते हैं clone। इन्हें दो कारणों से मूव कंस्ट्रक्शन या असाइनमेंट की आवश्यकता नहीं है: वे कॉपी करने योग्य नहीं हैं, और उनके पास पहले से ही एक अधिक कुशल प्राकृतिक "चाल" ऑपरेशन है - आप बस ऑब्जेक्ट को पॉइंटर को कॉपी / स्थानांतरित करते हैं और ऑब्जेक्ट स्वयं नहीं करता है एक नए मेमोरी लोकेशन पर जाना होगा।

अधिकांश प्रकार उन दो श्रेणियों में से एक में आते हैं, लेकिन अन्य प्रकार के भी हैं जो उपयोगी भी हैं, बस दुर्लभ। विशेष रूप से std::unique_ptr, संसाधन के अद्वितीय स्वामित्व को व्यक्त करने वाले प्रकार, जैसे कि , स्वाभाविक रूप से केवल-प्रकार के होते हैं, क्योंकि वे मूल्य-जैसे नहीं होते हैं (यह उन्हें कॉपी करने के लिए कोई मतलब नहीं है) लेकिन आप उन्हें सीधे उपयोग करते हैं (हमेशा नहीं सूचक या संदर्भ द्वारा) और इसलिए इस प्रकार की वस्तुओं को एक स्थान से दूसरे स्थान पर ले जाना चाहते हैं।


61
क्या असली हर्ब सेटर कृपया खड़े होंगे? :)
fredoverflow

6
हाँ, मैंने एक OAuth Google खाते का उपयोग करके दूसरे पर स्विच किया और मुझे यहां दिए गए दो लॉगिन को मर्ज करने का तरीका खोजने के लिए परेशान नहीं होना चाहिए। (अभी तक OAuth के खिलाफ एक और तर्क बहुत अधिक सम्मोहक लोगों के बीच।) मैं शायद दूसरे एक का फिर से उपयोग नहीं करूंगा, इसलिए यह मैं कभी-कभार एसओ पद के लिए उपयोग करूंगा।
हर्ब सटर

7
मैंने सोचा कि std::mutexयह अचल था, क्योंकि POSIX म्यूटेक्स पते द्वारा उपयोग किया जाता है।
प्यूपी

9
@ एससीपुरिन: दरअसल, इसे हर्बोवरफ्लो कहा जाता है।
sbi

26
यह बहुत अधिक उत्थान हो रहा है, क्या किसी ने यह नहीं देखा है कि जब कोई प्रकार केवल चाल होना चाहिए, तो यह सवाल नहीं है? :)
जोनाथन वेकली

18

वास्तव में जब मैं आसपास खोज करता हूं, तो मैंने पाया कि C ++ 11 में कुछ प्रकार चल नहीं रहे हैं:

  • सभी mutexप्रकार के ( recursive_mutex, timed_mutex, recursive_timed_mutex,
  • condition_variable
  • type_info
  • error_category
  • locale::facet
  • random_device
  • seed_seq
  • ios_base
  • basic_istream<charT,traits>::sentry
  • basic_ostream<charT,traits>::sentry
  • सभी atomicप्रकार
  • once_flag

जाहिर तौर पर क्लैंग पर एक चर्चा है: https://groups.google.com/forum/?fromgroups=# .topic/comp.std.c++/pCO1Qqb3Xa4


1
... पुनरावृत्तियों चल नहीं होना चाहिए ?! क्या क्यों?
user541686

हाँ, मुझे लगता है कि iterators / iterator adaptorsइसे संपादित किया जाना चाहिए क्योंकि C ++ 11 में Move_iterator है?
बिलज

ठीक है अब मैं सिर्फ भ्रमित हूँ। क्या आप पुनरावृत्तियों के बारे में बात कर रहे हैं जो अपने लक्ष्य को स्थानांतरित करते हैं , या स्वयं पुनरावृत्तियों को स्थानांतरित करने के बारे में ?
user541686

1
तो है std::reference_wrapper। ठीक है, दूसरों को वास्तव में गैर-जंगम लगते हैं।
क्रिश्चियन राऊ

1
इन तीन श्रेणियों में आते लगते हैं: 1. निम्न स्तर संगामिति से संबंधित प्रकार (एटोमिक्स, mutexes), 2. बहुरूपी आधार वर्ग ( ios_base, type_info, facet), 3. मिश्रित अजीब सामान ( sentry)। संभवत: केवल औसत वर्ग के एक औसत प्रोग्रामर ही दूसरी श्रेणी में लिखेंगे।
फिलिप

0

एक और कारण मैंने पाया है - प्रदर्शन। कहते हैं कि आपके पास एक वर्ग 'ए' है जो एक मूल्य रखता है। आप एक इंटरफ़ेस को आउटपुट करना चाहते हैं जो उपयोगकर्ता को सीमित समय के लिए मान (गुंजाइश के लिए) बदलने की अनुमति देता है।

इसे प्राप्त करने का एक तरीका 'स्कोप गार्ड' ऑब्जेक्ट 'ए' से लौटा है जो मूल्य को उसके विध्वंसक में वापस सेट करता है, जैसे:

class a 
{ 
    int value = 0;

  public:

    struct change_value_guard 
    { 
        friend a;
      private:
        change_value_guard(a& owner, int value) 
            : owner{ owner } 
        { 
            owner.value = value;
        }
        change_value_guard(change_value_guard&&) = delete;
        change_value_guard(const change_value_guard&) = delete;
      public:
        ~change_value_guard()
        {
            owner.value = 0;
        }
      private:
        a& owner;
    };

    change_value_guard changeValue(int newValue)
    { 
        return{ *this, newValue };
    }
};

int main()
{
    a a;
    {
        auto guard = a.changeValue(2);
    }
}

अगर मैंने change_value_guard को चल बनाया है, तो मुझे उसके विध्वंसक के लिए एक 'if' जोड़ना होगा जो यह जाँच करेगा कि क्या गार्ड से ले जाया गया है - यह एक अतिरिक्त अगर, और एक प्रदर्शन प्रभाव है।

हाँ, यकीन है, यह शायद किसी भी समझदार ऑप्टिमाइज़र द्वारा अनुकूलित किया जा सकता है, लेकिन फिर भी यह अच्छा है कि भाषा (इसके लिए C ++ 17 की आवश्यकता होती है, हालांकि एक गैर-चल प्रकार को वापस करने में सक्षम होने के लिए गारंटीकृत प्रतिलिपि आवश्यक है) की आवश्यकता नहीं है यह भुगतान करने के लिए कि यदि हम किसी भी तरह से गार्ड को स्थानांतरित करने के लिए नहीं जा रहे हैं, तो इसे बनाने वाले फ़ंक्शन (न ही पे-फॉर-व्हाट-यू-न-यूज़ सिद्धांत) को वापस करने के अलावा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.