कच्चे, कमजोर_प्रतिमा, अनूठे_प्रति, साझा_प्रति आदि… उन्हें बुद्धिमानी से कैसे चुनें?


33

C ++ में बहुत सारे संकेत हैं लेकिन 5 साल में ईमानदार होने के लिए C ++ प्रोग्रामिंग में (विशेष रूप से Qt फ्रेमवर्क के साथ) मैं केवल पुराने कच्चे पॉइंटर का उपयोग करता हूं:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

मुझे पता है कि बहुत सारे अन्य "स्मार्ट" संकेत हैं:

// shared pointer:
shared_ptr<SomeKindofObject> Object;

// unique pointer:
unique_ptr<SomeKindofObject> Object;

// weak pointer:
weak_ptr<SomeKindofObject> Object;

लेकिन मुझे इस बात का ज़रा सा भी अंदाज़ा नहीं है कि उनके साथ क्या करना है और कच्चे पॉइंटर्स की तुलना में वे मुझे क्या ऑफर कर सकते हैं।

उदाहरण के लिए मेरे पास यह श्रेणी शीर्षक है:

#ifndef LIBRARY
#define LIBRARY

class LIBRARY
{
public:
    // Permanent list that will be updated from time to time where
    // each items can be modified everywhere in the code:
    QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings; 
private:
    // Temporary reader that will read something to put in the list
    // and be quickly deleted:
    QSettings *_reader;
    // A dialog that will show something (just for the sake of example):
    QDialog *_dialog;
};

#endif 

यह स्पष्ट रूप से संपूर्ण नहीं है, लेकिन इन 3 बिंदुओं में से प्रत्येक के लिए उन्हें "कच्चा" छोड़ना ठीक है या मुझे कुछ और उपयुक्त उपयोग करना चाहिए?

और दूसरी बार में, यदि कोई नियोक्ता कोड पढ़ेगा, तो क्या वह इस बात पर सख्त होगा कि मैं किस प्रकार के पॉइंटर्स का उपयोग करता हूं या नहीं?


यह विषय SO के लिए इतना उपयुक्त लगता है। यह 2008 में था । और यहाँ किस तरह के पॉइंटर का उपयोग मैं कब करता हूं? । मुझे यकीन है कि आप और भी बेहतर मैच पा सकते हैं। ये थे सिर्फ पहली मैं देखा
sehe

imo इस एक सीमा के बाद से यह इन वर्गों के वैचारिक अर्थ / इरादे के बारे में उतना ही है जितना कि यह उनके व्यवहार और कार्यान्वयन के तकनीकी विवरण के बारे में है। चूंकि पूर्व की ओर स्वीकार किए गए उत्तर में झूठ है, इसलिए मैं उस SO प्रश्न का "PSE संस्करण" होने से खुश हूं।
Ixrec

जवाबों:


70

एक "कच्चा" सूचक अप्रबंधित है। वह है, निम्नलिखित पंक्ति:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

... यदि एक साथ साथ deleteउचित समय पर क्रियान्वित नहीं किया जाता है तो मेमोरी को लीक कर देगा ।

auto_ptr

इन मामलों को कम करने के लिए, std::auto_ptr<>पेश किया गया था। 2011 के मानक से पहले C ++ की सीमाओं के कारण, auto_ptrस्मृति को लीक करना अभी भी बहुत आसान है । यह इस तरह के रूप में सीमित मामलों के लिए पर्याप्त है, हालांकि:

void func() {
    std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
    // do some work
    // will not leak if you do not copy sKOO_ptr.
}

इसका सबसे कमजोर उपयोग-मामलों में से एक कंटेनर में है। ऐसा इसलिए है क्योंकि यदि किसी की प्रतिलिपि auto_ptr<>बनाई गई है और पुरानी प्रतिलिपि को सावधानी से रीसेट नहीं किया गया है, तो कंटेनर पॉइंटर को हटा सकता है और डेटा खो सकता है।

unique_ptr

प्रतिस्थापन के रूप में, C ++ 11 पेश किया गया std::unique_ptr<>:

void func2() {
    std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());

    func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}

इस तरह के एक unique_ptr<>को सही ढंग से साफ किया जाएगा, भले ही यह फ़ंक्शन के बीच पारित हो। यह सूचक के "स्वामित्व" का शब्दार्थ रूप से प्रतिनिधित्व करता है - "स्वामी" इसे साफ करता है। यह कंटेनरों में उपयोग के लिए इसे आदर्श बनाता है:

std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();

इसके विपरीत auto_ptr<>, unique_ptr<>यहाँ अच्छी तरह से व्यवहार किया जाता है, और जब vectorआकार बदलता है, तो कोई भी वस्तु गलती से हटा नहीं दी जाएगी, जबकि vectorइसकी बैकिंग स्टोर की प्रतियां।

shared_ptr तथा weak_ptr

unique_ptr<>उपयोगी है, सुनिश्चित करने के लिए, लेकिन ऐसे मामले हैं जहां आप चाहते हैं कि आपके कोड आधार के दो हिस्से समान ऑब्जेक्ट को संदर्भित करने और पॉइंटर को चारों ओर कॉपी करने में सक्षम हों, जबकि अभी भी उचित सफाई की गारंटी दी जा रही है। उदाहरण के लिए, एक पेड़ इस तरह दिख सकता है, जब उपयोग कर रहा हो std::shared_ptr<>:

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

इस मामले में, हम एक रूट नोड की कई प्रतियों को भी पकड़ सकते हैं, और रूट नोड की सभी प्रतियां नष्ट होने पर पेड़ को अच्छी तरह से साफ किया जाएगा।

यह काम करता है क्योंकि प्रत्येक shared_ptr<>वस्तु को न केवल सूचक को रखता है, बल्कि उन सभी shared_ptr<>वस्तुओं का एक संदर्भ गणना भी है जो एक ही सूचक को संदर्भित करता है। जब कोई नया बनाया जाता है, तो गिनती बढ़ जाती है। जब एक नष्ट हो जाता है, तो गिनती नीचे जाती है। जब गिनती शून्य तक पहुंच जाती है, तो सूचक deleteडी होता है।

तो यह एक समस्या का परिचय देता है: डबल-लिंक्ड संरचनाएं परिपत्र संदर्भों के साथ समाप्त होती हैं। कहें कि हम parentअपने पेड़ में एक सूचक जोड़ना चाहते हैं Node:

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

अब, यदि हम हटाते हैं, तो Nodeइसका चक्रीय संदर्भ है। यह कभी भी deleted नहीं होगा क्योंकि इसकी संदर्भ संख्या कभी भी शून्य नहीं होगी।

इस समस्या को हल करने के लिए, आप एक का उपयोग करें std::weak_ptr<>:

template<class T>
struct Node {
    T value;
    std::weak_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

अब, चीजें सही तरीके से काम करेंगी, और एक नोड को हटाने से मूल नोड के लिए अटक संदर्भ नहीं छोड़ेगा। यह पेड़ को थोड़ा और जटिल बनाता है, हालांकि:

std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();

इस तरह, आप नोड के संदर्भ को लॉक कर सकते हैं, और जब आप इस पर काम कर रहे होते हैं, तो आपके पास एक उचित गारंटी है कि यह गायब नहीं होगा shared_ptr<>

make_shared तथा make_unique

अब, कुछ छोटी-मोटी समस्याएं हैं shared_ptr<>और unique_ptr<>जिनका समाधान किया जाना चाहिए। निम्नलिखित दो पंक्तियों में एक समस्या है:

foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());

यदि thrower()एक अपवाद फेंकता है, तो दोनों लाइनें मेमोरी को लीक कर देंगी। और उस से भी अधिक, shared_ptr<>संदर्भ गिनती रखती दूर वस्तु से दूर यह की ओर इशारा करता है और इस कर सकते हैं एक दूसरे आवंटन मतलब)। यह आमतौर पर वांछनीय नहीं है।

C ++ 11 प्रदान करता है std::make_shared<>()और C ++ 14 std::make_unique<>()इस समस्या को हल करने के लिए प्रदान करता है:

foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());

अब, दोनों मामलों में, भले ही thrower()एक अपवाद फेंकता हो, स्मृति का रिसाव नहीं होगा। एक बोनस के रूप में, अपने प्रबंधित ऑब्जेक्ट के रूप में एक ही मेमोरी स्पेस मेंmake_shared<>() अपनी संदर्भ गणना बनाने का अवसर है , जो दोनों तेजी से हो सकता है और आपको एक अपवाद सुरक्षा गारंटी देते हुए मेमोरी के कुछ बाइट्स बचा सकता है!

Qt के बारे में नोट्स

हालांकि, यह ध्यान दिया जाना चाहिए, कि क्यूटी, जो पूर्व-सी ++ 11 संकलक का समर्थन करना चाहिए, का अपना कचरा-संग्रह मॉडल है: कई QObjectएस में एक तंत्र है जहां वे उपयोगकर्ता की आवश्यकता के बिना उन्हें ठीक से नष्ट कर deleteदेंगे।

मुझे नहीं पता कि QObjectC ++ 11 प्रबंधित पॉइंटर्स द्वारा प्रबंधित किए जाने पर s कैसे व्यवहार करेगा, इसलिए मैं यह नहीं कह सकता कि shared_ptr<QDialog>यह एक अच्छा विचार है। मुझे निश्चित रूप से कहने के लिए Qt के साथ पर्याप्त अनुभव नहीं है, लेकिन मेरा मानना ​​है कि Qt5 को इस उपयोग के मामले के लिए समायोजित किया गया है।


1
@Zilators: कृपया क्यूटी के बारे में मेरी टिप्पणी पर ध्यान दें। तीनों बिंदुओं को प्रबंधित किया जाना चाहिए या नहीं, इस बारे में आपके प्रश्न का उत्तर इस बात पर निर्भर करता है कि क्या Qt ऑब्जेक्ट अच्छा व्यवहार करेंगे।
ग्रेफेड

2
"दोनों पॉइंटर को रखने के लिए अलग-अलग आवंटन करते हैं"? नहीं, Unique_ptr कभी भी कुछ भी अतिरिक्त नहीं आवंटित करता है, केवल साझा_ptr को संदर्भ-गणना + आवंटनकर्ता-ऑब्जेक्ट आवंटित करना चाहिए। "दोनों लाइनें स्मृति को लीक कर देंगी"? नहीं, केवल बुरा व्यवहार की गारंटी नहीं हो सकती है।
Deduplicator

1
@Deduplicator: मेरा शब्दांकन स्पष्ट नहीं होना चाहिए: shared_ptrएक अलग ऑब्जेक्ट है - एक अलग आवंटन - newएड ऑब्जेक्ट से। वे विभिन्न स्थानों में मौजूद हैं। make_sharedएक ही स्थान पर उन्हें एक साथ रखने की क्षमता है, जो अन्य चीजों के अलावा, कैश स्थानीयता में सुधार करता है।
ग्रेफेड

2
@ ग्रेफेड: नोनोनो। shared_ptrएक वस्तु है। और एक वस्तु का प्रबंधन करने के लिए, इसे एक (संदर्भ-गणना (कमजोर + मजबूत) + विध्वंसक) -object आवंटित करना होगा। make_sharedएक टुकड़े के रूप में उस और प्रबंधित वस्तु को आवंटित करने की अनुमति देता है। unique_ptrउन का उपयोग नहीं करता है, इस प्रकार कोई संगत लाभ नहीं है, यह सुनिश्चित करने से अलग है कि वस्तु हमेशा स्मार्ट-पॉइंटर के स्वामित्व में है। एक अलग रूप में के रूप में, एक एक हो सकता है shared_ptrजो मालिक एक अंतर्निहित वस्तु और एक का प्रतिनिधित्व करता है nullptr, या जो खुद नहीं करता है और एक गैर nullpointer प्रतिनिधित्व करता है।
डेडुप्लिकेटर

1
मैंने इसे देखा, और इसके बारे में एक सामान्य भ्रम प्रतीत होता है कि क्या shared_ptrकरता है: 1. यह किसी वस्तु का स्वामित्व साझा करता है (एक आंतरिक गतिशील रूप से आवंटित वस्तु द्वारा कमजोर और एक मजबूत संदर्भ गणना, साथ ही एक डीलेटर का प्रतिनिधित्व करता है) । 2. इसमें एक सूचक होता है। वे दो हिस्से स्वतंत्र हैं। make_uniqueऔर make_sharedदोनों यह सुनिश्चित करते हैं कि आवंटित वस्तु को एक स्मार्ट-पॉइंटर में सुरक्षित रूप से रखा गया है। इसके अलावा, make_sharedस्वामित्व-ऑब्जेक्ट और प्रबंधित सूचक को एक साथ आवंटित करने की अनुमति देता है।
Deduplicator
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.