সি ++ 11 লাম্বদা বাস্তবায়ন এবং মেমরির মডেল


97

আমি সি ++ 11 সমাপ্তি সম্পর্কে কীভাবে সঠিকভাবে চিন্তা করতে পারি এবং std::functionসেগুলি কীভাবে প্রয়োগ করা হয় এবং কীভাবে মেমরি পরিচালনা করা হয় সে সম্পর্কে আমি কিছু তথ্য চাই ।

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

অতএব আমি কখন সি ++ ল্যাম্বডাস ব্যবহার করবেন বা ব্যবহার করবেন না সে সম্পর্কে আরও ভাল ধারণা বিকাশ করতে চাই।

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

আমি কল্পনা করি যে একটি রিয়েল-টাইম সিস্টেমে আমি ল্যাম্বডা ফাংশনগুলির একটি শৃঙ্খলা স্থাপন করতে পেরেছি, বি এর সাথে একটি ধারাবাহিকতা যুক্তি হিসাবে পাস করেছি, যাতে একটি প্রসেসিং পাইপলাইন A->Bতৈরি হয়। এই ক্ষেত্রে, এ এবং বি ক্লোজারগুলি একবার বরাদ্দ করা হবে। যদিও আমি নিশ্চিত নই যে এগুলি স্ট্যাকের বা গাদাতে বরাদ্দ দেওয়া হবে কিনা। তবে সাধারণভাবে এটি রিয়েল-টাইম সিস্টেমে ব্যবহার করা নিরাপদ বলে মনে হয়। অন্যদিকে যদি বি কিছু ল্যাম্বদা ফাংশন সি তৈরি করে, যা এটি ফিরে আসে, তবে সি এর জন্য মেমরি বরাদ্দ করা হবে এবং বারবার ক্ষয় করা হবে, যা রিয়েল-টাইম ব্যবহারের জন্য গ্রহণযোগ্য হবে না।

সিউডো কোডে, একটি ডিএসপি লুপ, যা আমি মনে করি রিয়েল-টাইম নিরাপদ হতে চলেছে। আমি প্রসেসিং ব্লক A এবং তারপরে বি সম্পাদন করতে চাই, যেখানে A তার যুক্তি কল করে। এই দুটি ফাংশনই std::functionবস্তুগুলি ফেরত দেয় , সুতরাং fএকটি std::functionবস্তু হবে, যেখানে এর পরিবেশটি গাদাতে সংরক্ষণ করা হয়:

auto f = A(B);  // A returns a function which calls B
                // Memory for the function returned by A is on the heap?
                // Note that A and B may maintain a state
                // via mutable value-closure!
for (t=0; t<1000; t++) {
    y = f(t)
}

এবং যা আমার মনে হয় রিয়েল-টাইম কোডে ব্যবহার করা খারাপ হতে পারে:

for (t=0; t<1000; t++) {
    y = A(B)(t);
}

এবং এমন এক যেখানে আমার মনে হয় স্ট্যাক মেমরি সম্ভবত বন্ধের জন্য ব্যবহৃত হবে:

freq = 220;
A = 2;
for (t=0; t<1000; t++) {
    y = [=](int t){ return sin(t*freq)*A; }
}

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

এটা কি সঠিক? ধন্যবাদ.


4
ল্যাম্বডা এক্সপ্রেশন ব্যবহার করার সময় কোনও ওভারহেড নেই। অন্য পছন্দটি হ'ল এই জাতীয় কোনও ফাংশন অবজেক্ট নিজে লিখুন, যা হুবহু একই রকম। বিটিডব্লিউ, ইনলাইন প্রশ্নে, যেহেতু সংকলকটির প্রয়োজনীয় সমস্ত তথ্য রয়েছে, এটি নিশ্চিতভাবে কলটিতে ইনলাইন করতে পারে operator()। কোনও "উত্তোলন" করার দরকার নেই, ল্যাম্বডাস বিশেষ কিছু নয়। তারা স্থানীয় ফাংশন অবজেক্টের জন্য কেবলমাত্র একটি স্বল্প হাত।
Xeo

এটি একটি প্রশ্ন বলে মনে হচ্ছে যদি এটি std::functionতার রাজ্যকে গাদা করে রাখে বা না রাখে এবং ল্যাম্বডাসের সাথে তার কোনও সম্পর্ক নেই। এটা কি সঠিক?
মাকিং হাঁস

8
কেবল যে কোনো ভুল বোঝাবুঝি ক্ষেত্রে এটি বানান: একটি ল্যামডা অভিব্যক্তি না একটি std::function!!
Xeo

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

4
সি ++ ১৪ এর পর থেকে স্টিভ করুন আপনি কোনও ফাংশন থেকে একটি autoরিটার্নের ধরণ দিয়ে ল্যাম্বদা ফিরতে পারেন।
Oktalist

উত্তর:


104

আমার বর্তমান বোধগম্যতা হল যে কোনও ল্যাপডা ক্যাপচার বন্ধ না করে হ'ল সি কলব্যাকের মতো। যাইহোক, যখন পরিবেশটি হয় মান দ্বারা বা রেফারেন্স দ্বারা ক্যাপচার করা হয় তখন স্ট্যাকের উপর একটি বেনামে বস্তু তৈরি হয়।

না; এটি সর্বদা একটি অজানা প্রকারের সি ++ অবজেক্ট, স্ট্যাকের উপরে তৈরি। একটি ক্যাপচার-কম ল্যামডা যাবে রূপান্তরিত একটি ফাংশন পয়েন্টার মধ্যে (যদিও তা সি নিয়মাবলী কলিং জন্য উপযুক্ত বাস্তবায়ন নির্ভরশীল), কিন্তু তার মানে এই নয় যে এটা হল একটি ফাংশন পয়েন্টার।

যখন কোনও ফাংশন থেকে কোনও মান-ক্লোজার ফিরে আসতে হবে, একজন এটি স্টাড :: ফাংশনে আবদ্ধ করে। এক্ষেত্রে ক্লোজার স্মৃতিতে কী ঘটে?

একটি ল্যাম্বদা সি ++ 11 তে বিশেষ কিছু নয়। এটি অন্য কোনও বস্তুর মতো একটি বস্তু। একটি ল্যাম্বডা এক্সপ্রেশনটির ফলে অস্থায়ী হয়, যা স্ট্যাকের উপর একটি ভেরিয়েবল আরম্ভ করতে ব্যবহৃত হতে পারে:

auto lamb = []() {return 5;};

lambএকটি স্ট্যাক অবজেক্ট। এটির একটি নির্মাতা এবং ধ্বংসকারী রয়েছে। এবং এটি এর জন্য সমস্ত সি ++ নিয়ম অনুসরণ করবে। প্রকারভেদে lambমানগুলি / রেফারেন্সগুলি ধারণ করা হবে; তারা অন্য কোনও ধরণের অন্যান্য অবজেক্ট সদস্যের মতোই সেই অবজেক্টের সদস্য হবে।

আপনি এটি একটি দিতে পারেন std::function:

auto func_lamb = std::function<int()>(lamb);

এই ক্ষেত্রে এটির মানটির একটি অনুলিপি পাবেন lamb। যদি lambমান অনুসারে কোনও কিছু ক্যাপচার করত তবে সেই মানগুলির দুটি অনুলিপি থাকত; একটি ইন lamb, এবং একটি ভিতরে func_lamb

বর্তমান সুযোগটি শেষ func_lambহয়ে lambগেলে, স্ট্যাক ভেরিয়েবলগুলি পরিষ্কার করার নিয়ম অনুসারে ধ্বংস হয়ে যাবে এবং তারপরে হবে।

আপনি গাদা হিসাবে সহজেই একটি বরাদ্দ করতে পারে:

auto func_lamb_ptr = new std::function<int()>(lamb);

ঠিক যেখানে চলার বিষয়বস্তুর জন্য মেমরিটি std::functionবাস্তবায়ন নির্ভর, তবে std::functionসাধারণত নিযুক্ত টাইপ-ইরেজরের জন্য কমপক্ষে একটি মেমরি বরাদ্দ থাকা প্রয়োজন। এই কারণেই std::functionএর নির্মাতা একটি বরাদ্দ নিতে পারে।

যখনই std :: ফাংশনটি মুক্ত হয় তখনই কি এটি মুক্ত হয়, অর্থাত্ এটি কি স্টাডি :: শেয়ারড_পিটারের মতো রেফারেন্স-গণনা করা হয়?

std::functionএর সামগ্রীগুলির একটি অনুলিপি সঞ্চয় করে । কার্যত প্রতিটি স্ট্যান্ডার্ড লাইব্রেরি সি ++ প্রকারের মতো মান শব্দার্থকfunction ব্যবহার করে । সুতরাং, এটি অনুলিপিযোগ্য; এটি অনুলিপি করা হলে, নতুন অবজেক্টটি সম্পূর্ণ পৃথক। এটি চলনযোগ্যও, সুতরাং কোনও বরাদ্দ এবং অনুলিপি ছাড়াই কোনও অভ্যন্তরীণ বরাদ্দ যথাযথভাবে স্থানান্তর করা যেতে পারে।function

সুতরাং রেফারেন্স গণনা করার প্রয়োজন নেই।

"মেমরি বরাদ্দ" "" রিয়েল-টাইম কোডে ব্যবহার করা খারাপ "এর সমান বলে ধরে নিয়ে আপনি যে সমস্ত কিছু বলছেন তা সঠিক।


4
দুর্দান্ত ব্যাখ্যা, আপনাকে ধন্যবাদ। সুতরাং সৃষ্টিটি std::functionসেই বিন্দু যেখানে মেমরিটি বরাদ্দ এবং অনুলিপি করা হয়। এটি অনুসরণ করে দেখে মনে হচ্ছে যে কোনও বন্ধে ফিরে আসার কোনও উপায় নেই (যেহেতু তারা স্ট্যাকের উপর বরাদ্দ করা হয়েছে), প্রথমে কোনওটিতে অনুলিপি না করে std::function?
স্টিভ

4
@ স্টিভ: হ্যাঁ; সুযোগটি থেকে বেরিয়ে আসার জন্য আপনাকে কোনও ধরণের পাত্রে একটি ল্যাম্বডা মুড়ে রাখতে হবে।
নিকল বোলাস

পুরো ফাংশনের কোডটি অনুলিপি করা হয়েছে, বা মূল ফাংশনটি সংকলন-সময়-বরাদ্দ এবং ক্লোজড ওভার মানগুলি পাস করেছে?
লামাডেডন

আমি যুক্ত করতে চাই যে মানটি কম-বেশি পরোক্ষভাবে আদেশ দেয় (§ 20.8.11.2.1 [func.wrap.func.con] ¶ 5) যে কোনও ল্যাম্বদা যদি কিছু ক্যাপচার না করে তবে std::functionএটি গতিশীল মেমরি ছাড়াই কোনও বস্তুতে সংরক্ষণ করা যেতে পারে বরাদ্দ চলছে।
5gon12eder

4
@ ইয়াক্ক: আপনি কীভাবে "বৃহত" সংজ্ঞায়িত করবেন? রাষ্ট্রের দুটি পয়েন্টার যুক্ত একটি বস্তু কি "বড়"? কিভাবে 3 বা 4? এছাড়াও, বস্তুর আকার একমাত্র সমস্যা নয়; যদি অবজেক্টটি নো-চলনযোগ্য নয়, তবে এটি অবশ্যই একটি বরাদ্দে সংরক্ষণ করতে হবে, যেহেতু functionএকটি নোসেসপ্লেভ মুভ কনস্ট্রাক্টর রয়েছে। "সাধারণত প্রয়োজন" বলার পুরো বিষয়টি হ'ল আমি " সর্বদা প্রয়োজন" বলি না : এমন পরিস্থিতি রয়েছে যেখানে কোনও বরাদ্দ কার্যকর করা হবে না।
নিকল বোলাস

1

সি ++ ল্যাম্বদা হ'ল একটি সিনট্যাকটিক চিনির চারপাশে (বেনামে) অতিরিক্ত লোডযুক্ত ফান্টর ক্লাস operator()এবং কলযোগ্যগুলির std::functionচারপাশে কেবল একটি মোড়ক (যেমন ফান্টেক্টর, ল্যাম্বডাস , সি-ফাংশন, ...) যা বর্তমান থেকে "শক্ত ল্যাম্বডা অবজেক্ট" মূল্য দ্বারা অনুলিপি করে স্ট্যাক স্কোপ - গাদা

প্রকৃত নির্মাণকারীর সংখ্যা / পুনঃস্থাপনের পরীক্ষা করার জন্য আমি একটি পরীক্ষা করেছি (শেয়ারড_প্টারে মোড়ানোর অন্য স্তর ব্যবহার করে তবে এটি ক্ষেত্রে হয় না)। নিজের জন্য দেখুন:

#include <memory>
#include <string>
#include <iostream>

class Functor {
    std::string greeting;
public:

    Functor(const Functor &rhs) {
        this->greeting = rhs.greeting;
        std::cout << "Copy-Ctor \n";
    }
    Functor(std::string _greeting="Hello!"): greeting { _greeting } {
        std::cout << "Ctor \n";
    }

    Functor & operator=(const Functor & rhs) {
        greeting = rhs.greeting;
        std::cout << "Copy-assigned\n";
        return *this;
    }

    virtual ~Functor() {
        std::cout << "Dtor\n";
    }

    void operator()()
    {
        std::cout << "hey" << "\n";
    }
};

auto getFpp() {
    std::shared_ptr<std::function<void()>> fp = std::make_shared<std::function<void()>>(Functor{}
    );
    (*fp)();
    return fp;
}

int main() {
    auto f = getFpp();
    (*f)();
}

এটি এই আউটপুট তোলে:

Ctor 
Copy-Ctor 
Copy-Ctor 
Dtor
Dtor
hey
hey
Dtor

স্ট্যাক-বরাদ্দ ল্যাম্বডা অবজেক্টের জন্য ঠিক একই সেট / ডিটারের সেট কল করা হবে! (এখন স্টার বরাদ্দের জন্য এটি সেন্টারকে কল করে, কপি-কর্টর (+ হিপ বরাদ্দ) এটি স্ট্যান্ড :: ফাংশনে নির্মাণের জন্য এবং অন্যটি শেয়ারড_পিটিআর হিপ বরাদ্দ + ফাংশন নির্মাণের জন্য)

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.