আমি কীভাবে একটি স্ট্যান্ড :: অনন্য_পিটার সদস্যের সাথে একটি কাস্টম মুছুন ব্যবহার করতে পারি?


133

আমার একটি অনন্য_পিটার সদস্য সহ একটি ক্লাস রয়েছে।

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
সি ++ 11 সহstd::unique_ptr<Bar, decltype(&destroy)> ptr_;
জো

1
এই সমাধানটির বিপরীতে হ'ল এটি প্রত্যেকের ওভারহেড দ্বিগুণ করে unique_ptr(তাদের অবশ্যই ফাংশন পয়েন্টারটি প্রকৃত ডেটার সাথে পয়েন্টার সহ সংরক্ষণ করতে হবে), প্রতিবার ধ্বংস ফাংশনটি পাস করার প্রয়োজন হয়, এটি ইনলাইন করতে পারে না (যেহেতু টেমপ্লেটটি পারবে না নির্দিষ্ট ফাংশনে বিশেষজ্ঞ, শুধুমাত্র স্বাক্ষর) এবং অবশ্যই পয়েন্টারের মাধ্যমে ফাংশনটি কল করতে হবে (সরাসরি কলের চেয়ে বেশি ব্যয়বহুল)। উভয় rici এবং Deduplicator এর উত্তর একটি functor করতে বিশেষজ্ঞ দ্বারা এই খরচ সব এড়ানো।
শ্যাডোর্যাঞ্জার

@ শ্যাডোএ্যাঞ্জার এটি ডিফল্ট_ডিলিট <টি> এবং সঞ্চিত ফাংশন পয়েন্টারটিকে প্রতিবার সংজ্ঞায়িত করে না আপনি এটি স্পষ্টভাবে পাস করেন কিনা?
হার্গোট

117

সি ++ 11 (জি ++ 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); });

এর সাহায্যে আপনি চেষ্টা / আওয়াজ ধরার প্রয়োজন ছাড়াই, আরআইআই ব্যবহার করে ব্যতিক্রম-নিরাপদ ক্লিনআপের সুবিধা পাবেন।


2
এই উত্তর হওয়া উচিত, ইমো। এটি আরও সুন্দর সমাধান। বা কোনও ডাউনসাইড যেমন, যেমন std::functionসংজ্ঞা রাখা বা এ জাতীয় মত?
j00hi

17
@ j00hi, আমার মতে এই সমাধানটির কারণে এই সমাধানটির অপ্রয়োজনীয় ওভারহেড রয়েছে std::function। গৃহীত উত্তরের মতো লাম্বদা বা কাস্টম শ্রেণি এই সমাধানের বিপরীতে অন্তর্ভুক্ত হতে পারে। আপনি যখন ডেডিকেটেড মডিউলটিতে সমস্ত বাস্তবায়ন আলাদা করতে চান তবে এই পদ্ধতির সুবিধা রয়েছে advantage
ম্যাগরাস

5
এটি স্ট্যামি :: ফাংশন কনস্ট্রাক্টর নিক্ষেপ করলে মেমরি ফাঁস হবে (যা ল্যাম্বডা
স্ট্যান্ড

4
লাম্বদা কি এখানে সত্যই দরকার? এটি সহজ হতে পারে deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);যদি customdeleterকনভেনশন অনুসরণ করে (এটি বাতিল হয়ে যায় এবং কাঁচা পয়েন্টারটিকে আর্গুমেন্ট হিসাবে গ্রহণ করে)।
ভিক্টর পোলেভয়

এই পদ্ধতির একটি খারাপ দিক রয়েছে। std :: ফাংশনটি মুভ কনস্ট্রাক্টর যখনই সম্ভব ব্যবহার করার প্রয়োজন নেই। এর অর্থ হ'ল আপনি যখন std :: পদক্ষেপ (my_deleted_unique_ptr) করবেন তখন ল্যাম্বদা দ্বারা আবদ্ধ সামগ্রীগুলি সরানোর পরিবর্তে সম্ভবত অনুলিপি করা হবে যা আপনি যা চান তা হতে পারে বা নাও হতে পারে।
জিনিয়াসআইসমে

71

আপনার কেবল একটি মুছক ক্লাস তৈরি করতে হবে:

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

এবং এটির টেমপ্লেট আর্গুমেন্ট হিসাবে সরবরাহ করুন unique_ptr। আপনাকে এখনও আপনার নির্মাতাদের অনন্য_পাত্রটি শুরু করতে হবে:

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

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

আমি যতদূর জানি, সমস্ত জনপ্রিয় সি ++ গ্রন্থাগারগুলি এটি সঠিকভাবে প্রয়োগ করে; যেহেতু BarDeleter আসলে কোনও রাজ্য নেই, তাই এর কোনও স্থান দখল করার প্রয়োজন নেই unique_ptr


8
এই বিকল্পটি কেবলমাত্র অ্যারে, এসটিডি :: ভেক্টর এবং অন্যান্য সংগ্রহের সাথে কাজ করে যেহেতু এটি শূন্য প্যারামিটার ব্যবহার করতে পারে এসটিডি :: অনন্য_পিটার কনস্ট্রাক্টর। অন্যান্য উত্তরগুলি সমাধানগুলি ব্যবহার করে যা এই শূন্য প্যারামিটার কনস্ট্রাক্টরের অ্যাক্সেস নেই কারণ একটি অনন্য পয়েন্টার তৈরি করার সময় একটি ডিলিটার উদাহরণ অবশ্যই সরবরাহ করতে হবে। তবে এই দ্রবণটি একটি (ডিফল্ট struct BarDeleter) ক্লাস ( ) থেকে std::unique_ptr( std::unique_ptr<Bar, BarDeleter>) সরবরাহ করে যা std::unique_ptrকনস্ট্রাক্টরকে তার নিজের উপর একটি ডিলিটার উদাহরণ তৈরি করতে দেয় । যেমন নিম্নলিখিত কোড অনুমোদিতstd::unique_ptr<Bar, BarDeleter> bar[10];
ডেভিডএফ

13
আমি সহজেই ব্যবহারের জন্য একটি typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
টাইপিডেফ

@ ডেভিডএফ: বা ডেলিপ্লিকেটর এর পদ্ধতির ব্যবহার করুন , যার একই সুবিধা রয়েছে (ইনলাইনিং মুছে ফেলা, প্রত্যেকের জন্য অতিরিক্ত সঞ্চয়স্থান unique_ptrনেই, নির্মাণের সময় মুছে ফেলার কোনও উদাহরণ সরবরাহ করার প্রয়োজন নেই), এবং std::unique_ptr<Bar>স্মরণে না রেখে কোথাও ব্যবহার করতে সক্ষম হওয়ার সুবিধা যুক্ত করে বিশেষ typedefবা স্পষ্টরূপে সরবরাহকারী দ্বিতীয় টেম্পলেট প্যারামিটার ব্যবহার করতে। (স্পষ্টরূপে বলতে
গেলে

22

রানটাইমের সময় আপনি যদি মুছে ফেলা পরিবর্তন করতে সক্ষম না হন তবে আমি কাস্টম মোছার প্রকারটি দৃ strongly়তার সাথে সুপারিশ করব। উদাহরণস্বরূপ, যদি আপনার মুছে ফেলার জন্য কোনও ফাংশন পয়েন্টার ব্যবহার করে sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*),। অন্য কথায়, unique_ptrঅবজেক্টের অর্ধেক বাইট অপচয় হয়।

যদিও প্রতিটি ক্রিয়াকলাপ মোড়ানোর জন্য কাস্টম মোছার লিখন লেখা একটি বিরক্তিকর। ধন্যবাদ, আমরা ফাংশনটিতে একটি টাইপযুক্ত লিখতে পারি:

সি ++ 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()};

সি ++ 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_uniqueসি ++ 20 পোস্টের বিশেষায়নের অনুমতি নেই (কারণ এটি আগে করা উচিত নয়) কারণ C ++ 20 এমন জিনিসগুলির বিশেষত্বকে অস্বীকার করে stdযা শ্রেণীর টেম্পলেট নয় ( std::make_uniqueএকটি ফাংশন টেম্পলেট)। মনে রাখবেন যে আপনি পয়েন্টারটি std::unique_ptr<Bar>বরাদ্দ না করে create(), তবে অন্য কিছু বরাদ্দ ফাংশন থেকে সঞ্চিত হয়ে গেলে আপনি সম্ভবত ইউবি দিয়ে শেষ করবেন ।
জাস্টিন 20

আমি নিশ্চিত যে এটি অনুমোদিত। আমার কাছে মনে হচ্ছে এটি প্রমাণ করা শক্ত যে এই বিশেষায়িতকরণটি 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একটি পরিচিত ধ্বংস ফাংশন, যেখানে subclassing ঘটতে যাচ্ছে না ব্যবহার করে), এবং এটি হিসাবে অনেক দূরে সবচেয়ে সহজ পদ্ধিতি হল পদ্ধতির দ্বারা এর আপনার বাকী কোডটি কেবল 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);});
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.