স্ট্যান্ড :: ফাংশন কীভাবে প্রয়োগ করা হয়?


100

আমি যে সূত্রগুলি পেয়েছি তার মতে, একটি ল্যাম্বডা এক্সপ্রেশনটি মূলত ওভারলোডেড ফাংশন কল অপারেটর এবং সদস্য হিসাবে রেফারেন্সড ভেরিয়েবলের সাথে ক্লাস তৈরি করে কম্পাইলার দ্বারা প্রয়োজনীয়ভাবে প্রয়োগ করা হয়। এটি পরামর্শ দেয় যে ল্যাম্বডা এক্সপ্রেশনগুলির আকার পরিবর্তিত হয় এবং পর্যাপ্ত রেফারেন্সের ভেরিয়েবল দেয় যে আকারটি নির্বিচারে বড় হতে পারে

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


4
আমি std::functionকিছুক্ষণ আগে জিসিসি / স্টাডলিব বাস্তবায়নের দিকে নজর রেখেছিলাম । এটি মূলত একটি বহুকোষযুক্ত বস্তুর জন্য একটি হ্যান্ডেল শ্রেণি। অভ্যন্তরীণ বেস শ্রেণীর একটি উত্পন্ন ক্লাসটি গাদাগুলিতে বরাদ্দকৃত পরামিতিগুলি ধরে রাখার জন্য তৈরি করা হয় - তারপরে এটির পয়েন্টারটি একটি সাবোবজেক্ট হিসাবে ধরা হয় std::function। আমি বিশ্বাস করি এটি std::shared_ptrঅনুলিপি এবং চলন পরিচালনা করতে পছন্দসই গণনা ব্যবহার করে ।
অ্যান্ড্রু তোমাজস

4
নোট করুন যে বাস্তবায়নগুলি যাদু ব্যবহার করতে পারে, অর্থাত্ আপনার কাছে উপলভ্য নয় এমন সংকলক এক্সটেনশনের উপর নির্ভর করে। এটি আসলে কিছু ধরণের বৈশিষ্ট্যের জন্য প্রয়োজনীয়। বিশেষত, ট্রাম্পোলাইনগুলি একটি জ্ঞাত প্রযুক্তি যা স্ট্যান্ডার্ড সি ++ এ অনুপলব্ধ।
এমএসএলটার

উত্তর:


80

এর বাস্তবায়ন std::functionএকের প্রয়োগের থেকে অন্যটিতে পৃথক হতে পারে তবে মূল ধারণাটি এটি টাইপ-ইরেজর ব্যবহার করে। এটি করার একাধিক উপায় থাকা সত্ত্বেও, আপনি কল্পনা করতে পারেন একটি তুচ্ছ (অনুকূল নয়) সমাধান এর মতো হতে পারে ( std::function<int (double)>সরলতার জন্য নির্দিষ্ট ক্ষেত্রে সহজতর করা ):

struct callable_base {
   virtual int operator()(double d) = 0;
   virtual ~callable_base() {}
};
template <typename F>
struct callable : callable_base {
   F functor;
   callable(F functor) : functor(functor) {}
   virtual int operator()(double d) { return functor(d); }
};
class function_int_double {
   std::unique_ptr<callable_base> c;
public:
   template <typename F>
   function(F f) {
      c.reset(new callable<F>(f));
   }
   int operator()(double d) { return c(d); }
// ...
};

এই সাধারণ পদ্ধতির মধ্যে functionঅবজেক্টটি কেবলমাত্র unique_ptrএকটি বেস ধরণের সংরক্ষণ করতে পারে। এর সাথে ব্যবহৃত প্রতিটি বিভিন্ন ফান্টারের জন্য function, বেস থেকে প্রাপ্ত একটি নতুন ধরণের তৈরি হয় এবং সেই ধরণের কোনও বস্তু গতিশীলভাবে ইনস্ট্যান্ট হয়। std::functionবস্তু একই আকারের সর্বদা এবং গাদা বিভিন্ন functors জন্য প্রয়োজনীয় স্থান বরাদ্দ হবে।

বাস্তব জীবনে বিভিন্ন অপ্টিমাইজেশন রয়েছে যা পারফরম্যান্স সুবিধাগুলি সরবরাহ করে তবে উত্তরটি জটিল করে তুলবে। প্রকারটি ছোট অবজেক্ট অপটিমাইজেশন ব্যবহার করতে পারে, ডায়নামিক প্রেরণাকে একটি ফ্রি-ফাংশন পয়েন্টার দ্বারা প্রতিস্থাপন করা যেতে পারে যা ফান্টরকে এক মাত্রার ইন্ডিয়ারেশন এড়ানোর জন্য যুক্তি হিসাবে গ্রহণ করে ... তবে ধারণাটি মূলত একই রকম।


অনুলিপিগুলি কীভাবে std::functionআচরণ করে তা সম্পর্কিত বিষয়ে, একটি দ্রুত পরীক্ষা ইঙ্গিত দেয় যে রাষ্ট্র ভাগ করে নেওয়ার চেয়ে অভ্যন্তরীণ কলযোগ্য বস্তুর অনুলিপিগুলি সম্পন্ন হয়।

// g++4.8
int main() {
   int value = 5;
   typedef std::function<void()> fun;
   fun f1 = [=]() mutable { std::cout << value++ << '\n' };
   fun f2 = f1;
   f1();                    // prints 5
   fun f3 = f1;
   f2();                    // prints 5
   f3();                    // prints 6 (copy after first increment)
}

পরীক্ষাটি নির্দেশ করে যে উল্লেখযোগ্য f2সত্তার একটি অনুলিপি পেয়েছে, পরিবর্তে একটি রেফারেন্সের চেয়ে বেশি। কলযোগ্য সত্তাটি যদি বিভিন্ন std::function<>বস্তু দ্বারা ভাগ করা হয় তবে প্রোগ্রামটির আউটপুট 5, 6, 7 হত।


@ কোল "কোল 9" জনসন অনুমান করে তিনি নিজেই এটি লিখেছেন
অ্যারোনম্যান

8
@ কোল "কোল 9" জনসন: এটি বাস্তব কোডটির একটি ওভারসিম্প্লিফিকেশন, আমি এটি কেবল ব্রাউজারে টাইপ করেছি, যাতে এটিতে টাইপস থাকতে পারে এবং / অথবা বিভিন্ন কারণে সংকলন করতে ব্যর্থ হতে পারে। উত্তরের কোডটি কেবল সেখানে কীভাবে ক্ষয় হয় / কীভাবে প্রয়োগ করা যায় তা উপস্থাপন করতে পারে, এটি পরিষ্কারভাবে উত্পাদন মানের কোড নয়।
ডেভিড রদ্রিগেজ - ড্রিবাস

4
@ মুভিংডাক: আমি বিশ্বাস করি ল্যাম্বডাস অনুলিপিযোগ্য (৫.১.২ / ১৯), তবে এটি প্রশ্ন নয়, বরং std::functionঅভ্যন্তরীণ বস্তুটি অনুলিপি করা হলে এর শব্দার্থবিজ্ঞান সঠিক হবে কিনা , এবং আমি মনে করি না যে এটি ঘটবে to (মনে ল্যামডা যে যেমনটি একটি মান এবং চপল, একটি ভিতরে সঞ্চিত std::function, যদি ফাংশন রাষ্ট্রের কপির সংখ্যা কপি করা হয়েছে std::functionযা অবাঞ্ছিত হয় ভিতরে একটি প্রমিত অ্যালগরিদম বিভিন্ন ফলাফল, হতে পারে।
ডেভিড রদ্রিগেজ - dribeas

4
@ মিক্লাস হোমল্যা: আমি জি ++ ৪.৮ দিয়ে পরীক্ষা করেছি এবং প্রয়োগটি অভ্যন্তরীণ অবস্থার অনুলিপি করে। কলযোগ্য সত্তা যদি গতিশীল বরাদ্দের প্রয়োজনের জন্য যথেষ্ট পরিমাণে বড় হয় তবে তার অনুলিপি std::functionএকটি বরাদ্দকে ট্রিগার করবে।
ডেভিড রদ্রিগেজ - ড্রিবিস

4
@ ডেভিডরডগ্রিজেজ-ড্রিবিয়াস ভাগ করে নেওয়া রাষ্ট্রটি অনাকাঙ্ক্ষিত হবে, কারণ ছোট অবজেক্টের অপ্টিমাইজেশনের অর্থ হ'ল আপনি একটি সংকলক এবং সংকলক সংস্করণ নির্ধারিত আকারের থ্রেশোল্ডে শেয়ারড স্টেট থেকে শেয়ারড স্টেশনে যাবেন (ছোট অবজেক্ট অপটিমাইজেশন শেয়ারড স্টেটকে ব্লক করবে)। সমস্যা মনে হচ্ছে।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

23

@ ডেভিড রদ্রিগিজের উত্তর - ড্রিবিয়াস টাইপ-ইরেজ প্রদর্শনের জন্য ভাল তবে টাইপ-ইরেজারে কীভাবে কপি করা হয় তাও অন্তর্ভুক্ত নয় (এর উত্তরে ফাংশন অবজেক্টটি কপি-গঠনযোগ্য হবে না)। এই আচরণগুলি functionফান্টারের ডেটার পাশাপাশি বস্তুতেও সংরক্ষণ করা হয় ।

উবুন্টু 14.04 জিসিসি 4.8 থেকে এসটিএল বাস্তবায়নে ব্যবহৃত কৌশলটি হ'ল একটি জেনেরিক ফাংশন রচনা করা, এটি প্রতিটি সম্ভাব্য ফান্টারের ধরণের সাথে বিশেষজ্ঞ করা, এবং তাদের সর্বজনীন ফাংশন পয়েন্টার টাইপে ফেলে দেওয়া। সুতরাং টাইপ তথ্য মুছে ফেলা হয়

আমি এর একটি সরলীকৃত সংস্করণ আঁকিয়েছি। আশা করি এটি সাহায্য করবে

#include <iostream>
#include <memory>

template <typename T>
class function;

template <typename R, typename... Args>
class function<R(Args...)>
{
    // function pointer types for the type-erasure behaviors
    // all these char* parameters are actually casted from some functor type
    typedef R (*invoke_fn_t)(char*, Args&&...);
    typedef void (*construct_fn_t)(char*, char*);
    typedef void (*destroy_fn_t)(char*);

    // type-aware generic functions for invoking
    // the specialization of these functions won't be capable with
    //   the above function pointer types, so we need some cast
    template <typename Functor>
    static R invoke_fn(Functor* fn, Args&&... args)
    {
        return (*fn)(std::forward<Args>(args)...);
    }

    template <typename Functor>
    static void construct_fn(Functor* construct_dst, Functor* construct_src)
    {
        // the functor type must be copy-constructible
        new (construct_dst) Functor(*construct_src);
    }

    template <typename Functor>
    static void destroy_fn(Functor* f)
    {
        f->~Functor();
    }

    // these pointers are storing behaviors
    invoke_fn_t invoke_f;
    construct_fn_t construct_f;
    destroy_fn_t destroy_f;

    // erase the type of any functor and store it into a char*
    // so the storage size should be obtained as well
    std::unique_ptr<char[]> data_ptr;
    size_t data_size;
public:
    function()
        : invoke_f(nullptr)
        , construct_f(nullptr)
        , destroy_f(nullptr)
        , data_ptr(nullptr)
        , data_size(0)
    {}

    // construct from any functor type
    template <typename Functor>
    function(Functor f)
        // specialize functions and erase their type info by casting
        : invoke_f(reinterpret_cast<invoke_fn_t>(invoke_fn<Functor>))
        , construct_f(reinterpret_cast<construct_fn_t>(construct_fn<Functor>))
        , destroy_f(reinterpret_cast<destroy_fn_t>(destroy_fn<Functor>))
        , data_ptr(new char[sizeof(Functor)])
        , data_size(sizeof(Functor))
    {
        // copy the functor to internal storage
        this->construct_f(this->data_ptr.get(), reinterpret_cast<char*>(&f));
    }

    // copy constructor
    function(function const& rhs)
        : invoke_f(rhs.invoke_f)
        , construct_f(rhs.construct_f)
        , destroy_f(rhs.destroy_f)
        , data_size(rhs.data_size)
    {
        if (this->invoke_f) {
            // when the source is not a null function, copy its internal functor
            this->data_ptr.reset(new char[this->data_size]);
            this->construct_f(this->data_ptr.get(), rhs.data_ptr.get());
        }
    }

    ~function()
    {
        if (data_ptr != nullptr) {
            this->destroy_f(this->data_ptr.get());
        }
    }

    // other constructors, from nullptr, from function pointers

    R operator()(Args&&... args)
    {
        return this->invoke_f(this->data_ptr.get(), std::forward<Args>(args)...);
    }
};

// examples
int main()
{
    int i = 0;
    auto fn = [i](std::string const& s) mutable
    {
        std::cout << ++i << ". " << s << std::endl;
    };
    fn("first");                                   // 1. first
    fn("second");                                  // 2. second

    // construct from lambda
    ::function<void(std::string const&)> f(fn);
    f("third");                                    // 3. third

    // copy from another function
    ::function<void(std::string const&)> g(f);
    f("forth - f");                                // 4. forth - f
    g("forth - g");                                // 4. forth - g

    // capture and copy non-trivial types like std::string
    std::string x("xxxx");
    ::function<void()> h([x]() { std::cout << x << std::endl; });
    h();

    ::function<void()> k(h);
    k();
    return 0;
}

এসটিএল সংস্করণে কিছু অপ্টিমাইজেশন রয়েছে

  • construct_fএবং destroy_fকিছু বাইট সংরক্ষণ করতে (একটি অতিরিক্ত প্যারামিটার বলে যে কি করতে হবে তা সহ) এক ফাংশন পয়েন্টার মধ্যে মিশিয়ে দেওয়া হয়
  • কাঁচা পয়েন্টারগুলি এ ফাংশন পয়েন্টার সহ ফান্টেক্টর অবজেক্টটি সংরক্ষণ করতে ব্যবহৃত হয় union, যাতে যখন functionকোনও ফাংশন পয়েন্টার থেকে কোনও বস্তুটি তৈরি করা হয়, তখন এটি unionগাদা স্থানের পরিবর্তে সরাসরি সংরক্ষণ করা হবে

আমি এসটিএল বাস্তবায়ন সর্বোত্তম সমাধান নয় কারণ আমি কিছু দ্রুত বাস্তবায়ন সম্পর্কে শুনেছি । তবে আমি বিশ্বাস করি অন্তর্নিহিত প্রক্রিয়াটি একই।


20

নির্দিষ্ট ধরণের আর্গুমেন্টের জন্য ("যদি f এর টার্গেট কল কলযোগ্য বস্তু reference_wrapperবা একটি ফাংশন পয়েন্টার দিয়ে যায়"), std::functionএর কনস্ট্রাক্টর কোনও ব্যতিক্রম অস্বীকার করে, তাই ডায়নামিক মেমোরি ব্যবহার করা প্রশ্নটির বাইরে। এই ক্ষেত্রে, সমস্ত ডেটা সরাসরি std::functionঅবজেক্টের অভ্যন্তরে সংরক্ষণ করতে হবে ।

সাধারণ ক্ষেত্রে (ল্যাম্বডা কেস সহ) গতিশীল মেমরি ব্যবহার করে (আদর্শ বরাদ্দকারীর মাধ্যমে বা std::functionকনস্ট্রাক্টরের কাছে বরাদ্দকারীর মাধ্যমে ) প্রয়োগ বাস্তবায়নের উপযুক্ত হিসাবে মঞ্জুরিপ্রাপ্ত। স্ট্যান্ডার্ড সুপারিশ করে বাস্তবায়নগুলি এড়াতে পারলে গতিশীল মেমরি ব্যবহার করবেন না, তবে আপনি যেমনটি বলেছেন ঠিক যদি ফাংশন অবজেক্ট (বস্তু নয় std::function, তবে বস্তুটি এটির ভিতরে আবৃত করা হয়) তবে এটি প্রতিরোধের কোনও উপায় নেই, যেহেতু std::functionএকটি নির্দিষ্ট আকার আছে।

ব্যতিক্রম নিক্ষেপ করার এই অনুমতিটি সাধারণ নির্মাণকারী এবং অনুলিপি নির্মাণকারী উভয়কেই দেওয়া হয়, যা অনুলিপি করার সময়ও স্পষ্টতই গতিশীল মেমরির বরাদ্দকে মঞ্জুরি দেয়। চলাফেরার জন্য, গতিশীল মেমরির প্রয়োজনীয়তার কোনও কারণ নেই। মানটি এটি স্পষ্টভাবে নিষিদ্ধ করেছে বলে মনে হচ্ছে না, এবং সম্ভবত মুভটি যদি মোড়কানো বস্তুর প্রকারের মুভ কনস্ট্রাক্টরকে কল করতে পারে না তবে আপনি এটি ধরে নিতে সক্ষম হবেন যে যদি বাস্তবায়ন এবং আপনার অবজেক্ট উভয়ই বুদ্ধিমান হয় তবে চলন ঘটবে না কোন বরাদ্দ।


-6

একটি std::functionওভারলোডগুলি operator()এটিকে ফান্টর অবজেক্ট হিসাবে তৈরি করে, ল্যাম্বডা এর কাজ একইভাবে করে। এটি মূলত সদস্য ভেরিয়েবলগুলির সাথে একটি কাঠামো তৈরি করে যা operator()ফাংশনের অভ্যন্তরে প্রবেশ করা যায় । সুতরাং মাথায় রাখার প্রাথমিক ধারণাটি হ'ল ল্যাম্বডা হ'ল একটি বস্তু (যাকে ফান্টেক্টর বা ফাংশন অবজেক্ট বলা হয়) কোনও ফাংশন নয়। মানটি এড়াতে পারলে গতিশীল মেমরি ব্যবহার না করার কথা বলে।


4
কীভাবে সম্ভবত নির্বিচারে বড় ল্যাম্বডাস একটি নির্দিষ্ট আকারের সাথে ফিট করতে পারে std::function? এটাই এখানে মূল প্রশ্ন।
মিক্লিস হোমোল্যা

4
@ অ্যারোনম্যান: আমি গ্যারান্টি দিচ্ছি যে প্রতিটি std::functionবস্তু একই আকারের এবং লাম্বদাসের আকার নয়।
মাকিং হাঁস

5
@aarmanman একইভাবে প্রতিটি std::vector<T...> বস্তুর প্রকৃত বরাদ্দকরণ উদাহরণ / উপাদানের সংখ্যার চেয়ে পৃথক আকারের (কপিলটাইম) নির্দিষ্ট আকার রয়েছে।
সেহ

4
@ অ্যারোনম্যান: ঠিক আছে, আপনার স্ট্যাকওভারফ্লো প্রশ্নটি পাওয়া উচিত যা উত্তর দেয় যে কীভাবে std :: ফাংশনটি এমনভাবে প্রয়োগ করা হয় যাতে এতে নির্বিচারে আকারের
ল্যাম্বডাস

4
@aaronman: যখন callable সত্তা সেট করা থাকে, নির্মাণ, নিয়োগ করুন ... std::function<void ()> f;কোন প্রয়োজন নেই বরাদ্দ করা, std::function<void ()> f = [&]() { /* captures tons of variables */ };সম্ভবত বরাদ্দ। std::function<void()> f = &free_function;সম্ভবত কোনও বরাদ্দ দেয় না ...
ডেভিড রদ্রিগেজ - ড্রিবাস
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.