क्या std :: unique_ptr <T> T की पूरी परिभाषा जानना आवश्यक है?


248

मेरा हेडर में कुछ कोड है जो इस तरह दिखता है:

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

अगर मैं इस हेडर को एक cpp में शामिल करता हूं जिसमें Thingटाइप परिभाषा शामिल नहीं है , तो यह VS2010-SP1 के तहत संकलित नहीं करता है:

1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ में \ मेमोरी (2067) शामिल हैं: C2027 त्रुटि: अपरिभाषित प्रकार 'थिंग' का उपयोग

std::unique_ptrद्वारा प्रतिस्थापित std::shared_ptrऔर यह संकलित करता है।

इसलिए, मैं अनुमान लगा रहा हूं कि यह वर्तमान VS2010 std::unique_ptrका कार्यान्वयन है जिसे पूर्ण परिभाषा की आवश्यकता है और यह पूरी तरह से कार्यान्वयन-निर्भर है।

या यह है? क्या यह मानक आवश्यकताओं में कुछ है जो std::unique_ptrकेवल आगे की घोषणा के साथ काम करने के लिए असंभव है ? यह अजीब लगता है क्योंकि इसे केवल एक पॉइंटर को पकड़ना Thingचाहिए, क्या इसे नहीं करना चाहिए?


20
जब आप करते हैं और सी ++ 0x स्मार्ट पॉइंटर्स के साथ एक पूर्ण प्रकार की आवश्यकता नहीं है, तो हॉवर्ड हिनेंट के "अपूर्ण प्रकार और shared_ptr/ unique_ptr" अंत में तालिका को आपके प्रश्न का उत्तर देना चाहिए।
जेम्स मैकनेलिस

17
सूचक जेम्स के लिए धन्यवाद। मैं भूल गया था कि मैंने वह टेबल कहाँ रखी है! :-)
हावर्ड हिनान्ट


5
@JamesMcNellis हावर्ड हाइनेंट की वेबसाइट का लिंक नीचे है। यहां इसका web.archive.org संस्करण है। किसी भी मामले में, उन्होंने इसे पूरी तरह से एक ही सामग्री के साथ नीचे जवाब दिया :-)
एला 782

स्कॉट मेयर्स के प्रभावी आधुनिक C ++ के आइटम 22 में एक और अच्छी व्याख्या दी गई है।
फ्रेड स्कोन

जवाबों:


328

यहां से अपनाया गया ।

C ++ मानक लाइब्रेरी में अधिकांश टेम्प्लेट की आवश्यकता होती है कि उन्हें पूर्ण प्रकारों के साथ त्वरित किया जाए। हालांकि shared_ptrऔर unique_ptrकर रहे हैं आंशिक अपवाद नहीं। कुछ, लेकिन उनके सभी सदस्यों को अपूर्ण प्रकारों से त्वरित नहीं किया जा सकता है। इसके लिए प्रेरणा स्मार्ट पॉइंटर्स का उपयोग करके pimpl जैसे मुहावरों का समर्थन करना है , और अपरिभाषित व्यवहार को जोखिम में डाले बिना।

अधूरा व्यवहार तब हो सकता है जब आपके पास एक अपूर्ण प्रकार होता है और आप deleteउस पर कॉल करते हैं:

class A;
A* a = ...;
delete a;

ऊपर कानूनी कोड है। यह संकलन करेगा। आपका कंपाइलर उपरोक्त कोड के लिए चेतावनी का अनुकरण कर सकता है या नहीं कर सकता है। जब यह निष्पादित होता है, तो बुरी चीजें हो जाएंगी। यदि आप बहुत भाग्यशाली हैं तो आपका प्रोग्राम क्रैश हो जाएगा। हालाँकि एक अधिक संभावित परिणाम यह है कि आपका प्रोग्राम चुपचाप मेमोरी को लीक कर ~A()देगा क्योंकि इसे नहीं बुलाया जाएगा।

auto_ptr<A>उपरोक्त उदाहरण का उपयोग करने से मदद नहीं मिलती है। आपको अभी भी वही अपरिभाषित व्यवहार मिलता है जैसे कि आपने कच्चे पॉइंटर का इस्तेमाल किया हो।

फिर भी, कुछ स्थानों पर अपूर्ण कक्षाओं का उपयोग करना बहुत उपयोगी है! यह वह जगह है जहाँ shared_ptrऔर unique_ptrमदद करते हैं। इन स्मार्ट पॉइंटर्स में से एक का उपयोग आपको एक अपूर्ण प्रकार के साथ दूर जाने देगा, सिवाय इसके कि जहां एक पूर्ण प्रकार होना आवश्यक है। और सबसे महत्वपूर्ण बात, जब एक पूर्ण प्रकार का होना आवश्यक है, तो आप एक कंपाइल-टाइम त्रुटि प्राप्त करते हैं यदि आप उस बिंदु पर अपूर्ण प्रकार के साथ स्मार्ट पॉइंटर का उपयोग करने का प्रयास करते हैं।

अधिक अपरिभाषित व्यवहार नहीं:

यदि आपका कोड संकलित करता है, तो आपको हर जगह एक पूर्ण प्रकार का उपयोग करना होगा जो आपको चाहिए।

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptrऔर unique_ptrविभिन्न स्थानों में एक पूर्ण प्रकार की आवश्यकता होती है। कारण अस्पष्ट हैं, एक गतिशील डेलेटर बनाम एक स्थिर डेलेटर के साथ क्या करना है। सटीक कारण महत्वपूर्ण नहीं हैं। वास्तव में, अधिकांश कोड में आपके लिए वास्तव में यह जानना महत्वपूर्ण नहीं है कि एक पूर्ण प्रकार की आवश्यकता कहां है। बस कोड, और यदि आप इसे गलत पाते हैं, तो संकलक आपको बताएगा।

हालाँकि, अगर यह आपके लिए मददगार है, तो यहाँ एक तालिका है जो संपूर्णता आवश्यकताओं के संबंध में shared_ptrऔर उसके कई सदस्यों के दस्तावेज प्रस्तुत unique_ptrकरती है। यदि सदस्य को पूर्ण प्रकार की आवश्यकता होती है, तो प्रविष्टि में "सी" है, अन्यथा तालिका प्रविष्टि "आई" से भरी गई है।

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

पॉइंटर रूपांतरणों की आवश्यकता वाले किसी भी संचालन के लिए unique_ptrऔर दोनों के लिए पूर्ण प्रकार की आवश्यकता होती है shared_ptr

unique_ptr<A>{A*}निर्माता एक साथ प्राप्त कर सकते हैं अधूरा Aही संकलक के लिए एक कॉल स्थापित करने के लिए आवश्यक नहीं है यदि ~unique_ptr<A>()। उदाहरण के लिए यदि आप unique_ptrढेर लगाते हैं, तो आप एक अपूर्ण के साथ भाग सकते हैं A। इस बिंदु पर अधिक जानकारी यहाँ बैरीTheHatchet के उत्तर में पाई जा सकती है


3
बहुत बढ़िया जवाब। अगर मैं कर सकता था तो मैंने इसे +5 किया। मुझे यकीन है कि मैं अपनी अगली परियोजना में इसका उल्लेख करूंगा, जिसमें मैं स्मार्ट पॉइंटर्स का पूरा उपयोग करने का प्रयास कर रहा हूं।
मथियास

4
अगर कोई समझा सकता है कि मेज का क्या मतलब है, तो मुझे लगता है कि यह और अधिक लोगों की मदद करेगा
गीता

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

7
@ मेहरदाद: यह निर्णय C ++ 98 के लिए किया गया था, जो मेरे समय से पहले का है। हालाँकि मेरा मानना ​​है कि यह निर्णय कार्यान्वयन के बारे में एक चिंता का विषय था, और विनिर्देशन की कठिनाई (अर्थात वास्तव में कंटेनर के कुछ हिस्सों को पूर्ण प्रकार की आवश्यकता नहीं होती है)। आज भी, C ++ 98 के बाद से 15 वर्षों के अनुभव के साथ, यह इस क्षेत्र में दोनों कंटेनर विनिर्देश को आराम करने के लिए एक गैर-तुच्छ कार्य होगा, और यह सुनिश्चित करेगा कि आप महत्वपूर्ण कार्यान्वयन तकनीकों या अनुकूलन को आगे नहीं बढ़ाते हैं। मुझे लगता है कि यह किया जा सकता है। मुझे पता है कि यह बहुत काम आएगा। मैं एक व्यक्ति को प्रयास करने के बारे में जानता हूं।
हावर्ड हिनांत

9
क्योंकि यह उपरोक्त टिप्पणियों से स्पष्ट नहीं है, किसी के लिए भी यह समस्या है क्योंकि वे unique_ptrएक वर्ग के सदस्य चर के रूप में परिभाषित करते हैं , बस स्पष्ट रूप से वर्ग घोषणा में (हेडर फ़ाइल में) एक विध्वंसक (और निर्माता) घोषित करते हैं और उन्हें परिभाषित करने के लिए आगे बढ़ते हैं। स्रोत फ़ाइल में (और स्रोत फ़ाइल में वर्ग की पूरी घोषणा के साथ हेडर डाल) कम्पाइलर ऑटो को रोकने के लिए हेडर फ़ाइल (जो त्रुटि को ट्रिगर करता है) में कंस्ट्रक्टर या विध्वंसक को रोकने के लिए। stackoverflow.com/a/13414884/368896 भी मुझे यह याद दिलाने में मदद करता है।
डैन निसेनबाम

42

कंपाइलर को MyClass के लिए डिफॉल्ट डिस्ट्रक्टर बनाने के लिए थिंग की परिभाषा की आवश्यकता है। यदि आप स्पष्ट रूप से विध्वंसक घोषित करते हैं और इसके (खाली) कार्यान्वयन को CPP फ़ाइल में स्थानांतरित करते हैं, तो कोड को संकलित करना चाहिए।


5
मुझे लगता है कि यह एक डिफ़ॉल्ट फ़ंक्शन का उपयोग करने का सही मौका है। MyClass::~MyClass() = default;कार्यान्वयन फ़ाइल में अनजाने में बाद में सड़क से नीचे किसी के द्वारा हटाए जाने की संभावना कम लगती है जो किसी व्यक्ति द्वारा नष्ट किए गए शरीर को जानबूझकर खाली छोड़ने के बजाय मिटा दिया गया था।
डेनिस ज़िकफ़ोज़

@ डेनिस ज़िकफ़ोज़: दुर्भाग्य से ओपी वीसी ++ का उपयोग कर रहा है, और वीसी ++ अभी defaultएड और deleteडी क्लास के सदस्यों का समर्थन नहीं करता है ।
इलडार्जन

6
+1 .cpp फ़ाइल में डोर को स्थानांतरित करने के लिए कैसे। यह भी लगता MyClass::~MyClass() = defaultहै कि इसे क्लैंग पर कार्यान्वयन फ़ाइल में स्थानांतरित नहीं किया गया है। (अभी तक?)
Eonil

आपको कंस्ट्रक्टर के कार्यान्वयन को CPP फ़ाइल में स्थानांतरित करने की आवश्यकता है, कम से कम VS 2017 पर। उदाहरण के लिए यह उत्तर देखें: stackoverflow.com/a/27624369/5124002
jciloa

15

यह कार्यान्वयन-निर्भर नहीं है। कारण यह है कि काम करता है क्योंकि shared_ptrरन-टाइम पर कॉल करने के लिए सही डिस्ट्रक्टर को निर्धारित करता है - यह टाइप सिग्नेचर का हिस्सा नहीं है। हालाँकि, unique_ptrविध्वंसक इसके प्रकार का हिस्सा है, और इसे संकलन-समय पर अवश्य जाना चाहिए।


8

ऐसा लगता है कि वर्तमान उत्तर बिल्कुल सही नहीं बता रहे हैं कि डिफ़ॉल्ट कंस्ट्रक्टर (या डिस्ट्रक्टर) समस्या क्यों है, लेकिन खाली में घोषित रिक्त स्थान नहीं है।

यहाँ क्या हो रहा है:

यदि बाहरी वर्ग (यानी MyClass) के पास कंस्ट्रक्टर या विध्वंसक नहीं है, तो कंपाइलर डिफ़ॉल्ट रूप से उत्पन्न करता है। इसके साथ समस्या यह है कि कंपाइलर अनिवार्य रूप से .hpp फ़ाइल में डिफ़ॉल्ट खाली कंस्ट्रक्टर / डिस्ट्रक्टर को सम्मिलित करता है। इसका मतलब यह है कि डिफॉल्ट कंस्ट्रक्टर / डिस्ट्रक्टर का कोड होस्ट एक्जीक्यूटेबल के बाइनरी के साथ संकलित हो जाता है, न कि आपके लाइब्रेरी के बायनेरिज़ के साथ। हालाँकि यह परिभाषा वास्तव में आंशिक वर्गों का निर्माण नहीं कर सकती है। इसलिए जब लिंकर आपकी लाइब्रेरी के बाइनरी में जाता है और कंस्ट्रक्टर / डिस्ट्रक्टर को प्राप्त करने की कोशिश करता है, तो उसे कोई नहीं मिलता है और आपको त्रुटि मिलती है। यदि कंस्ट्रक्टर / डिस्ट्रक्टर कोड आपके .cpp में था तो आपके लाइब्रेरी बाइनरी में लिंकिंग के लिए उपलब्ध है।

यह Unique_ptr या shared_ptr का उपयोग करने से कोई लेना-देना नहीं है और अनूठे_ptr कार्यान्वयन के लिए पुराने VC ++ में बग को भ्रमित करना संभव प्रतीत होता है (VC ++ 2015 मेरी मशीन पर ठीक काम करता है)।

तो कहानी का नैतिक यह है कि आपके हेडर को किसी भी निर्माता / विध्वंसक परिभाषा से मुक्त रहने की आवश्यकता है। इसमें केवल उनकी घोषणा हो सकती है। उदाहरण के लिए, ~MyClass()=default;hpp में काम नहीं करेगा। यदि आप कंपाइलर को डिफ़ॉल्ट कंस्ट्रक्टर या डिस्ट्रक्टर डालने की अनुमति देते हैं, तो आपको एक लिंकर त्रुटि मिलेगी।

एक अन्य पक्ष नोट: यदि आप अभी भी cpp फ़ाइल में कंस्ट्रक्टर और विध्वंसक होने के बाद भी यह त्रुटि प्राप्त कर रहे हैं, तो सबसे अधिक संभावना यह है कि आपका पुस्तकालय ठीक से संकलित नहीं हो रहा है। उदाहरण के लिए, एक बार मैंने वीसी ++ में कंसोल से लाइब्रेरी में प्रोजेक्ट प्रकार को बस बदल दिया और मुझे यह त्रुटि मिली क्योंकि वीसी ++ ने _LIB प्रीप्रोसेसर प्रतीक नहीं जोड़ा और इससे सटीक त्रुटि संदेश उत्पन्न हुआ।


धन्यवाद! यह अविश्वसनीय रूप से अस्पष्ट सी ++ क्वर्क का बहुत ही संक्षिप्त विवरण था। मुझे बहुत तकलीफ से बचाया।
JPNotADragon

5

पूर्णता के लिए:

हेडर: आह

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

स्रोत A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

कक्षा B की परिभाषा को कंस्ट्रक्टर, डिस्ट्रक्टर और कुछ भी देखा जाना चाहिए जो कि अनुमानित रूप से बी को हटा सकता है (हालांकि कंस्ट्रक्टर उपरोक्त सूची में दिखाई नहीं देता है, VS2017 में भी कंस्ट्रक्टर को बी की परिभाषा की आवश्यकता है और विचार करते समय यह समझ में आता है। निर्माणकर्ता में एक अपवाद के मामले में unique_ptr फिर से नष्ट हो जाता है।)


1

टेम्पलेट तात्कालिकता के बिंदु पर थिंग की पूरी परिभाषा आवश्यक है। पिंपल मुहावरे का संकलन यही सटीक कारण है।

यदि यह संभव नहीं था, तो लोग इस तरह के सवाल नहीं पूछेंगे ।


-2

सरल उत्तर के बजाय केवल साझा किया गया है।


-7

मेरे लिए,

QList<QSharedPointer<ControllerBase>> controllers;

बस हेडर शामिल करें ...

#include <QSharedPointer>

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