कक्षा सदस्यों के लिए स्मार्ट पॉइंटर्स का उपयोग करना


159

मुझे C ++ 11 में कक्षा सदस्यों के रूप में स्मार्ट पॉइंटर्स के उपयोग को समझने में परेशानी हो रही है। मैंने स्मार्ट पॉइंटर्स के बारे में बहुत कुछ पढ़ा है और मुझे लगता है कि मुझे समझ में नहीं आता कि कैसे unique_ptrऔर सामान्य रूप से काम करता shared_ptr/ weak_ptrकरती हूं । जो मुझे समझ नहीं आ रहा है वह वास्तविक उपयोग है। ऐसा लगता है जैसे हर कोई unique_ptrलगभग हर समय जाने के तरीके के रूप में उपयोग करने की सलाह देता है । लेकिन मैं इस तरह से कुछ कैसे लागू करूंगा:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

मान लीजिए कि मैं बिंदुओं को स्मार्ट पॉइंटर्स के साथ बदलना चाहूंगा। A unique_ptrकाम नहीं करेगा क्योंकि getDevice(), ठीक है? तो वह समय है जब मैं उपयोग करता हूं shared_ptrऔर weak_ptr? उपयोग करने का कोई तरीका नहीं unique_ptr? मुझे लगता है कि ज्यादातर मामलों के लिए मुझे लगता shared_ptrहै जब तक मैं एक बहुत छोटे दायरे में एक सूचक का उपयोग कर रहा हूँ अधिक समझ में आता है?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

क्या वह रास्ता है? बहुत बहुत धन्यवाद!


4
यह जीवनकाल, स्वामित्व और संभावित नल के रूप में वास्तव में स्पष्ट होने में मदद करता है। उदाहरण के लिए, deviceकंस्ट्रक्टर के पास होने के बाद settings, क्या आप अभी भी कॉलिंग स्कोप में इसका उल्लेख कर सकते हैं या केवल इसके माध्यम से settings? यदि उत्तरार्द्ध, unique_ptrउपयोगी है। इसके अलावा, क्या आपके पास एक परिदृश्य है जहां रिटर्न का मूल्य getDevice()है null। यदि नहीं, तो एक संदर्भ वापस करें।
कीथ

2
हां, shared_ptr8/10 मामलों में ए सही है। अन्य 2/10 unique_ptrऔर के बीच विभाजित हैं weak_ptr। इसके अलावा, weak_ptrआम तौर पर परिपत्र संदर्भों को तोड़ने के लिए उपयोग किया जाता है; मुझे यकीन नहीं है कि आपका उपयोग सही माना जाएगा।
कोलिन डुपहाइन

2
सबसे पहले, आप deviceडेटा सदस्य के लिए क्या स्वामित्व चाहते हैं ? आपको सबसे पहले यह तय करना होगा।
juanchopanza

1
ठीक है, मैं समझता हूं कि फोन करने वाले के रूप में मैं unique_ptrइसके बजाय उपयोग कर सकता हूं और कंस्ट्रक्टर को कॉल करते समय स्वामित्व दे सकता हूं , अगर मुझे पता है कि मुझे अब इसके लिए आवश्यकता नहीं होगी। लेकिन Settingsकक्षा के डिजाइनर के रूप में मुझे नहीं पता कि क्या कॉलर एक संदर्भ भी रखना चाहता है। हो सकता है कि डिवाइस का उपयोग कई स्थानों पर किया जाएगा। ठीक है, शायद यही आपकी बात है। उस स्थिति में, मैं एकमात्र स्वामी नहीं रहूंगा और जब मैं share_ptr का उपयोग करूंगा, तो मुझे लगता है। और: इसलिए स्मार्ट पॉइंट पॉइंटर्स को रिप्लेस करते हैं, लेकिन रेफरेंस नहीं?
माइकल

this-> डिवाइस = डिवाइस; इसके अलावा आरंभीकरण सूचियों का उपयोग करें।
Nils

जवाबों:


202

A unique_ptrकाम नहीं करेगा क्योंकि getDevice(), ठीक है?

नहीं, जरूरी नहीं। आपकी वस्तु के लिए उपयुक्त स्वामित्व नीति को निर्धारित करने के लिए यहां क्या महत्वपूर्ण है Device, अर्थात आपके (स्मार्ट) पॉइंटर द्वारा बताई गई वस्तु का मालिक कौन होगा।

क्या यह अकेलेSettings वस्तु का उदाहरण बनने जा रहा है ? जब वस्तु नष्ट हो जाती है, तो क्या वस्तु को स्वचालित रूप से नष्ट कर दिया जाना चाहिए या उस वस्तु को नष्ट करना चाहिए?DeviceSettings

पहले मामले में, std::unique_ptrवह है जो आपको चाहिए, क्योंकि यह Settingsइंगित ऑब्जेक्ट का एकमात्र (अद्वितीय) स्वामी बनाता है, और एकमात्र वस्तु जो इसके विनाश के लिए जिम्मेदार है।

इस धारणा के तहत, getDevice()एक साधारण अवलोकन सूचक लौटना चाहिए (देखने वाले बिंदु ऐसे बिंदु हैं जो इंगित किए गए ऑब्जेक्ट को जीवित नहीं रखते हैं)। सूचक का सबसे सरल प्रकार एक कच्चा सूचक है:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ नोट १: आप सोच रहे होंगे कि मैं यहाँ कच्चे पॉइंटर्स का उपयोग क्यों कर रहा हूँ, जब हर कोई यह बताता रहता है कि कच्चे पॉइंटर्स खराब, असुरक्षित और खतरनाक हैं। कच्चे संकेत बुरा कर रहे हैं: वास्तव में, कि एक कीमती चेतावनी है, लेकिन यह सही संदर्भ में डाल करने के लिए महत्वपूर्ण है जब स्मृति प्रबंधन को प्रदर्शन करने के लिए प्रयोग किया जाता है , यानी आवंटन और के माध्यम से वस्तुओं deallocating newऔर delete। जब विशुद्ध रूप से संदर्भ शब्दार्थों को प्राप्त करने और गैर-मालिक के आसपास से गुजरने के लिए उपयोग किया जाता है, तो संकेत को देखते हुए, कच्चे पॉइंटर्स में आंतरिक रूप से खतरनाक कुछ भी नहीं है, शायद इस तथ्य के अलावा कि किसी को एक ख़तरनाक पॉइंटर को रोकने के लिए ध्यान नहीं देना चाहिए। - END नोट 1 ]

[ नोट 2: जैसा कि यह टिप्पणियों में उभरा, इस विशेष मामले में जहां स्वामित्व अद्वितीय है और स्वामित्व वाली वस्तु हमेशा मौजूद रहने की गारंटी है (यानी आंतरिक डेटा सदस्य deviceकभी नहीं होने वाला है nullptr), फ़ंक्शन getDevice()(और शायद चाहिए) एक सूचक के बजाय एक संदर्भ लौटाएं। हालांकि यह सच है, मैंने यहां एक कच्चे पॉइंटर को वापस करने का फैसला किया क्योंकि मेरा मतलब यह था कि यह एक छोटा जवाब होगा कि कोई उस मामले का सामान्यीकरण deviceकर सकता है nullptr, और यह दिखाने के लिए कि कच्चे पॉइंटर्स ठीक हैं जब तक कि कोई उनका उपयोग नहीं करता है मैनुअल मेमोरी प्रबंधन। - END नोट 2 ]


स्थिति मौलिक रूप से अलग है, ज़ाहिर है, अगर आपकी Settingsवस्तु में डिवाइस का अनन्य स्वामित्व नहीं होना चाहिए । यह मामला हो सकता है, उदाहरण के लिए, यदि Settingsवस्तु का विनाश इंगित Deviceऑब्जेक्ट के विनाश के रूप में अच्छी तरह से नहीं होना चाहिए ।

यह कुछ ऐसा है जो केवल आप अपने कार्यक्रम के एक डिजाइनर के रूप में बता सकते हैं; आपके द्वारा दिए गए उदाहरण से, मेरे लिए यह बताना मुश्किल है कि यह मामला है या नहीं।

यह पता लगाने में आपकी मदद करने के लिए, आप अपने आप से पूछ सकते हैं कि क्या इसके अलावा कोई अन्य वस्तुएं हैं Settingsजो Deviceवस्तु को जीवित रखने का हकदार हैं, जब तक कि वे केवल एक संकेतक पकड़ते हैं, बजाय केवल निष्क्रिय पर्यवेक्षक होने के। यदि वास्तव में ऐसा है, तो आपको एक साझा स्वामित्व नीति की आवश्यकता होती है , जो है std::shared_ptr:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

ध्यान दें, कि weak_ptrएक अवलोकन सूचक है, एक मालिक का सूचक नहीं है - दूसरे शब्दों में, यह इंगित की गई वस्तु को जीवित नहीं रखता है यदि इंगित किए गए अन्य सभी बिंदुओं के मालिक गुंजाइश से बाहर जाते हैं।

का लाभ weak_ptrएक नियमित रूप से कच्चे सूचक के ऊपर है कि आप सुरक्षित रूप से बता सकते हैं कि है weak_ptrहै झूलते या नहीं (यानी यह एक वैध वस्तु की ओर इशारा करते है या नहीं, या यदि वस्तु मूल रूप से करने के लिए नष्ट कर दिया गया बताया)। यह ऑब्जेक्ट expired()पर सदस्य फ़ंक्शन को कॉल करके किया जा सकता है weak_ptr


4
@ एलकेके: हाँ, सही है। A weak_ptrहमेशा कच्चे अवलोकनकर्ताओं का एक विकल्प है। यह एक मायने में अधिक सुरक्षित है, क्योंकि आप यह जाँच सकते हैं कि क्या यह इसे हटाने से पहले झूल रहा है, लेकिन यह कुछ ओवरहेड के साथ भी आता है। यदि आप आसानी से गारंटी दे सकते हैं कि आप झूलने वाले पॉइंटर को डिरेल करने नहीं जा रहे हैं, तो आपको कच्चे पॉइंटर्स को देखने के साथ ठीक होना चाहिए
एंडी प्रोल

6
पहले मामले में यह शायद getDevice()एक संदर्भ को वापस करने के लिए बेहतर होगा , यह पूरी तरह से नहीं? इसलिए कॉलर को चेक नहीं करना होगा nullptr
vobject

5
@chico: यकीन नहीं है कि तुम क्या मतलब है। auto myDevice = settings.getDevice()प्रकार का एक नया उदाहरण पैदा करेगा Deviceकहा जाता myDeviceहै और यह कि संदर्भ द्वारा संदर्भित एक से कॉपी-निर्माण getDevice()रिटर्न। यदि आप myDeviceएक संदर्भ बनना चाहते हैं , तो आपको करने की आवश्यकता है auto& myDevice = settings.getDevice()। इसलिए जब तक मैं कुछ याद नहीं कर रहा हूं, हम उसी स्थिति में वापस आ गए हैं जब हम उपयोग किए बिना थे auto
एंडी प्रोल

2
@ सुधार: क्योंकि आप ऑब्जेक्ट के स्वामित्व को दूर नहीं करना चाहते हैं - unique_ptrक्लाइंट के लिए एक परिवर्तनीय सौंपने से यह संभावना खुल जाती है कि ग्राहक इससे आगे बढ़ जाएगा, इस प्रकार स्वामित्व प्राप्त कर सकता है और आपको एक अशक्त (अद्वितीय) सूचक के साथ छोड़ देगा।
एंडी प्रोल

7
@ सुधार: जबकि वह एक ग्राहक को बढ़ने से रोकती है (जब तक ग्राहक पागल वैज्ञानिक उत्सुक const_castनहीं है), मैं व्यक्तिगत रूप से ऐसा नहीं करूंगा। यह एक कार्यान्वयन विवरण को उजागर करता है, अर्थात यह तथ्य कि स्वामित्व अद्वितीय है और एक के माध्यम से महसूस किया जाता है unique_ptr। मैं चीजों को इस तरह से देखता हूं: यदि आप स्वामित्व को पास / वापस करना चाहते हैं, तो स्मार्ट पॉइंटर पास करें / वापस करें ( unique_ptrया shared_ptr, स्वामित्व के प्रकार पर निर्भर करता है)। यदि आप स्वामित्व को पारित / वापस करने की आवश्यकता नहीं चाहते हैं, तो (ठीक से- constअयोग्य) सूचक या संदर्भ का उपयोग करें, ज्यादातर इस बात पर निर्भर करता है कि तर्क शून्य हो सकता है या नहीं।
एंडी प्रोल

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptrकेवल संदर्भ छोरों के लिए उपयोग किया जाता है। निर्भरता ग्राफ को एसाइक्लेक्लाइज्ड ग्राफ होना चाहिए। साझा किए गए पॉइंटर्स में 2 रेफरेंस काउंट होते हैं: shared_ptrएस के लिए 1 , और सभी पॉइंटर्स ( shared_ptrऔर weak_ptr) के लिए 1 । जब सभी shared_ptrको हटा दिया जाता है, तो पॉइंटर को हटा दिया जाता है। जब पॉइंटर की आवश्यकता होती है weak_ptr, lockतो पॉइंटर प्राप्त करने के लिए उपयोग किया जाना चाहिए, यदि यह मौजूद है।


इसलिए अगर मैं आपके उत्तर को सही ढंग से समझूं, तो स्मार्ट पॉइंटर्स कच्चे पॉइंटर्स को प्रतिस्थापित करते हैं, लेकिन जरूरी नहीं कि संदर्भ?
माइकल

क्या वास्तव में दो संदर्भ मायने रखते हैं shared_ptr? क्या आप कृपया बता सकते हैं कि क्यों? जहां तक ​​मैं समझता हूं, weak_ptrइसे गिना नहीं जाना चाहिए क्योंकि यह बस shared_ptrवस्तु पर काम करते समय एक नया बनाता है (यदि अंतर्निहित वस्तु अभी भी मौजूद है)।
ब्योर्न पोलेक्स

@ BjörnPollex: मैंने आपके लिए एक छोटा उदाहरण बनाया: लिंक । मैंने सब कुछ सिर्फ कॉपी कंस्ट्रक्टर्स पर लागू नहीं किया है और lockबढ़ावा संस्करण भी संदर्भ गिनती पर सुरक्षित (थ्रेड है deleteकेवल एक बार कहा जाता है)।
Naszta

@Naszta: आपका उदाहरण दिखाता है कि दो संदर्भ गणनाओं का उपयोग करके इसे लागू करना संभव है, लेकिन आपका उत्तर बताता है कि यह आवश्यक है , जो मुझे विश्वास नहीं है कि यह है। क्या आप कृपया अपने उत्तर में इसे स्पष्ट कर सकते हैं?
ब्योर्न पोलेक्स

1
@ BjörnPollex, weak_ptr::lock()यह बताने के लिए कि क्या वस्तु की अवधि समाप्त हो गई है, उसे "कंट्रोल ब्लॉक" का निरीक्षण करना चाहिए, जिसमें ऑब्जेक्ट का पहला संदर्भ काउंट और पॉइंटर है, इसलिए नियंत्रण ब्लॉक को नष्ट नहीं किया जाना चाहिए, जबकि weak_ptrउपयोग में आने वाली कोई वस्तु अभी भी नहीं है। इसलिए weak_ptrवस्तुओं की संख्या को ट्रैक किया जाना चाहिए, जो कि दूसरी संदर्भ गणना करता है। जब पहला रेफरी काउंट शून्य पर गिरता है तो ऑब्जेक्ट नष्ट हो जाता है, जब दूसरा रेफरी काउंट शून्य पर आ जाता है तो कंट्रोल ब्लॉक नष्ट हो जाता है।
जोनाथन वेकली
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.