मैं एक std :: unique_ptr सदस्य के साथ कस्टम डिलेटर का उपयोग कैसे करूँ?


133

मेरे पास एक Unique_ptr सदस्य है।

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};

बार एक तृतीय पक्ष वर्ग है जिसमें एक बनाएँ () फ़ंक्शन और एक नष्ट () फ़ंक्शन होता है।

अगर मैं std::unique_ptrएक स्टैंड फंक्शन में इसके साथ उपयोग करना चाहता था तो मैं कर सकता था:

void foo() {
    std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
    ...
}

क्या std::unique_ptrकिसी वर्ग के सदस्य के साथ ऐसा करने का कोई तरीका है ?

जवाबों:


133

कि मान लिया जाये createऔर destroyकर रहे हैं नि: शुल्क कार्य (जो ओ पी के कोड स्निपेट से मामला प्रतीत हो रहा है) के बाद हस्ताक्षरों के साथ:

Bar* create();
void destroy(Bar*);

आप अपनी कक्षा Fooको इस तरह लिख सकते हैं

class Foo {

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

    // ...

public:

    Foo() : ptr_(create(), destroy) { /* ... */ }

    // ...
};

ध्यान दें कि आपको किसी लैम्ब्डा या कस्टम डेलेटर को यहाँ लिखने की आवश्यकता नहीं है क्योंकि destroyपहले से ही डीलेटर है।


156
C ++ 11 के साथstd::unique_ptr<Bar, decltype(&destroy)> ptr_;
जो

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

@ShadowRanger इसे default_delete <T> और संग्रहीत फ़ंक्शन पॉइंटर को हर बार परिभाषित नहीं करता है कि आप इसे स्पष्ट रूप से पास करते हैं या नहीं?
हेरगोट

117

C ++ 11 (G ++ 4.8.2 में परीक्षण) में एक लैम्ब्डा का उपयोग करके इसे साफ करना संभव है।

इस पुन typedef: प्रयोज्य को देखते हुए :

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

तुम लिख सकते हो:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });

उदाहरण के लिए FILE*:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE* f) { fclose(f); });

इसके साथ आपको RAII का उपयोग करके अपवाद-सुरक्षित सफाई का लाभ मिलता है, बिना कोशिश / शोर को पकड़े।


2
यह उत्तर होना चाहिए, imo। यह अधिक सुंदर समाधान है। या क्या कोई डाउनसाइड्स हैं, जैसे std::functionकि परिभाषा या इस तरह के होने?
j00hi

17
@ j00hi, मेरी राय में इस समाधान के कारण अनावश्यक ओवरहेड है std::function। स्वीकार किए गए उत्तर में लैम्ब्डा या कस्टम वर्ग इस समाधान के विपरीत इनलाइन किया जा सकता है। लेकिन इस दृष्टिकोण के मामले में लाभ है जब आप समर्पित मॉड्यूल में सभी कार्यान्वयन को अलग करना चाहते हैं।
मगरास

5
यह मेमोरी को लीक कर देगा अगर std :: function constructor throws (जो हो सकता है अगर lambda std के अंदर फिट होने के लिए बहुत बड़ा है :: function ऑब्जेक्ट)
StaceyGirl

4
क्या वास्तव में यहां लंबोदर की आवश्यकता है? यह सरल हो सकता है deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);यदि customdeleterसम्मेलन का अनुसरण किया जाता है (यह शून्य देता है और एक तर्क के रूप में कच्चे सूचक को स्वीकार करता है)।
विक्टर पोलेवॉय

इस दृष्टिकोण के लिए एक नकारात्मक पहलू है। जब भी संभव हो std :: function को construct constructor का उपयोग करने की आवश्यकता नहीं होती है। इसका मतलब है कि जब आप std :: move (my_deleted_unique_ptr) करते हैं, तो लैम्ब्डा द्वारा संलग्न सामग्री को संभवतः स्थानांतरित करने के बजाय कॉपी किया जाएगा, जो आप चाहते हैं या नहीं हो सकता है।
GeniusIsme

71

आपको बस एक निपुण वर्ग बनाने की आवश्यकता है:

struct BarDeleter {
  void operator()(Bar* b) { destroy(b); }
};

और इसे टेम्पलेट तर्क के रूप में प्रदान करते हैं unique_ptr। आपको अभी भी अपने रचनाकारों में unique_ptr को इनिशियलाइज़ करना होगा:

class Foo {
  public:
    Foo() : bar(create()), ... { ... }

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};

जहां तक ​​मुझे पता है, सभी लोकप्रिय सी ++ लाइब्रेरी इसे सही ढंग से लागू करते हैं; चूंकि BarDeleter वास्तव में कोई राज्य नहीं है, इसलिए इसमें किसी भी स्थान पर कब्जा करने की आवश्यकता नहीं है unique_ptr


8
यह विकल्प केवल वही है जो सरणियों, std :: वेक्टर और अन्य संग्रहों के साथ काम करता है क्योंकि यह शून्य पैरामीटर std :: unique_ptr कंस्ट्रक्टर का उपयोग कर सकता है। अन्य उत्तर ऐसे समाधानों का उपयोग करते हैं, जिनके पास इस शून्य पैरामीटर निर्माता तक पहुंच नहीं है क्योंकि एक अद्वितीय सूचक का निर्माण करते समय एक डीलेटर उदाहरण प्रदान किया जाना चाहिए। लेकिन यह समाधान एक Deleter क्लास ( struct BarDeleter) से std::unique_ptr( std::unique_ptr<Bar, BarDeleter>) प्रदान करता है जो std::unique_ptrकंस्ट्रक्टर को अपने दम पर Deleter उदाहरण बनाने की अनुमति देता है । अर्थात निम्नलिखित कोड की अनुमति हैstd::unique_ptr<Bar, BarDeleter> bar[10];
डेविड जूल

13
मैं आसान उपयोग के लिए एक typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
टाईपेडिफ बनाऊंगा

@ डेविड: या डेडुप्लिकेटर के दृष्टिकोण का उपयोग करें , जिसमें समान फायदे हैं (हटाने को हटाने, प्रत्येक पर कोई अतिरिक्त भंडारण unique_ptrनहीं है, निर्माण करते समय डिलेटर का एक उदाहरण प्रदान करने की आवश्यकता नहीं है), और std::unique_ptr<Bar>याद रखने की आवश्यकता के बिना कहीं भी उपयोग करने में सक्षम होने के लाभ को जोड़ता है। विशेष typedefया स्पष्ट रूप से प्रदाता का उपयोग करने के लिए दूसरा टेम्पलेट पैरामीटर। (स्पष्ट होने के लिए, यह एक अच्छा समाधान है, मैंने मतदान किया, लेकिन यह एक निर्बाध समाधान का एक कदम शर्मीला ठहराव देता है)
शैडो रेंजर

22

जब तक आपको रन-वे पर डिलेटर को बदलने में सक्षम होने की आवश्यकता नहीं होती, मैं दृढ़ता से कस्टम डिलेटर प्रकार का उपयोग करने की सलाह दूंगा। उदाहरण के लिए, अपने Deleter के लिए एक समारोह सूचक उपयोग करते हैं, sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*)। दूसरे शब्दों में, unique_ptrवस्तु के बाइट्स का आधा हिस्सा बर्बाद हो जाता है।

हर फंक्शन को रैप करने के लिए एक कस्टम डिलेटर लिखना एक परेशानी है, हालाँकि। शुक्र है, हम फ़ंक्शन पर टेम्पर्ड टाइप कर सकते हैं:

C ++ 17 के बाद से:

template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

template <typename T, auto fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;

// usage:
my_unique_ptr<Bar, destroy> p{create()};

C ++ 17 से पहले:

template <typename D, D fn>
using deleter_from_fn = std::integral_constant<D, fn>;

template <typename T, typename D, D fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>;

// usage:
my_unique_ptr<Bar, decltype(destroy), destroy> p{create()};

निफ्टी। क्या मैं सही हूं कि यह वही लाभ प्राप्त करता है (मेमोरी ओवरहेड, फ़ंक्शन फ़ंक्शन पॉइंटर के बजाय सीधे कॉलिंग फ़ंक्शन, संभावित इनलाइनिंग कॉल पूरी तरह से दूर है) रिसी के जवाब से अंतिम संस्कारकर्ता के रूप में , बस कम बॉयलरप्लेट के साथ?
शैडो रेंजर

हां, यह एक कस्टम डिलेटर वर्ग के सभी लाभ प्रदान करना चाहिए, क्योंकि यही deleter_from_fnहै।
rmcclellan

7

आप जानते हैं, कस्टम डिलेटर का उपयोग करना सबसे अच्छा तरीका नहीं है, क्योंकि आपको अपने कोड में इसका उल्लेख करना होगा।
इसके बजाय, जब तक आपको नाम-स्तर की कक्षाओं में विशेषज्ञता जोड़ने की अनुमति दी जाती है::std जब तक कि कस्टम प्रकार शामिल नहीं होते हैं और आप शब्दार्थ का सम्मान करते हैं, ऐसा करें:

विशेषज्ञता std::default_delete:

template <>
struct ::std::default_delete<Bar> {
    default_delete() = default;
    template <class U, class = std::enable_if_t<std::is_convertible<U*, Bar*>()>>
    constexpr default_delete(default_delete<U>) noexcept {}
    void operator()(Bar* p) const noexcept { destroy(p); }
};

और शायद यह भी करते हैं std::make_unique():

template <>
inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() {
    auto p = create();
    if (!p) throw std::runtime_error("Could not `create()` a new `Bar`.");
    return { p };
}

2
मैं इससे बहुत सावधान रहूंगा। खोलने stdसे कीड़े की एक पूरी नई परत खुल जाती है। यह भी ध्यान दें कि की विशेषज्ञता को std::make_uniqueC ++ 20 के बाद की अनुमति नहीं है (इस तरह से पहले नहीं किया जाना चाहिए) क्योंकि C ++ 20 उन चीजों की विशेषज्ञता को अस्वीकार करता है stdजिनमें वर्ग टेम्पलेट नहीं हैं ( std::make_uniqueएक फ़ंक्शन टेम्पलेट है)। ध्यान दें कि आप संभवतः यूबी के साथ भी समाप्त हो जाएंगे यदि पॉइंटर को पास std::unique_ptr<Bar>से आवंटित नहीं किया गया था create(), लेकिन कुछ अन्य आवंटन फ़ंक्शन से।
जस्टिन

मैं आश्वस्त नहीं हूं कि इसकी अनुमति है। मुझे ऐसा लगता है कि यह साबित करना मुश्किल है कि std::default_deleteमूल टेम्पलेट की आवश्यकता को पूरा करने वाला यह विशेषज्ञता । मुझे लगता है कि std::default_delete<Foo>()(p)यह लिखने का एक वैध तरीका होगा delete p;, इसलिए यदि delete p;लिखने के लिए वैध होगा (यानी यदि Fooपूर्ण है), तो यह समान व्यवहार नहीं होगा। इसके अलावा, यदि delete p;लिखना अवैध था ( Fooअधूरा है), तो यह व्यवहार को std::default_delete<Foo>बनाए रखने के बजाय नए व्यवहार को निर्दिष्ट करेगा ।
जस्टिन

make_uniqueविशेषज्ञता समस्याग्रस्त है, लेकिन मैं निश्चित रूप से उपयोग किया है std::default_deleteअधिभार (साथ टेम्प्लेट की नहीं enable_if, बस OpenSSL की तरह सी structs के लिए BIGNUMएक ज्ञात विनाश समारोह, जहां उपवर्गीकरण होने वाला नहीं है का उपयोग करने वाले), और यह के रूप में, अब तक का सबसे आसान दृष्टिकोण से है अपने कोड के बाकी का उपयोग unique_ptr<special_type>फ़ंक्शनल टाइप पास करने की आवश्यकता के बिना कर सकते हैं जैसे कि Deleterसभी जगह टेम्प्लेट किया गया है, न ही उस समस्या से बचने के लिए उक्त प्रकार का नाम देने के लिए उपयोग typedef/ उपयोग usingकरना।
शैडो रेंजर

6

आप बस std::bindअपने नष्ट समारोह के साथ उपयोग कर सकते हैं ।

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
    std::placeholders::_1));

लेकिन निश्चित रूप से आप एक मेमने का उपयोग भी कर सकते हैं।

std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.