আমার কি স্টাড :: :: ফাংশন বা সি ++ এ কোনও ফাংশন পয়েন্টার ব্যবহার করা উচিত?


141

সি ++ এ কলব্যাক ফাংশনটি প্রয়োগ করার সময়, আমি এখনও সি-স্টাইলের ফাংশন পয়েন্টার ব্যবহার করব:

void (*callbackFunc)(int);

বা আমার স্ট্যান্ড :: ফাংশনটি ব্যবহার করা উচিত:

std::function< void(int) > callbackFunc;

9
যদি সংকলনের সময়ে কলব্যাক ফাংশনটি জানা থাকে তবে পরিবর্তে একটি টেম্পলেট বিবেচনা করুন।
বাউম মিট অউজেন

4
যখন বাস্তবায়নের একটি কলব্যাক ফাংশন আপনি যাই হোক না কেন আহ্বানকারী প্রয়োজন করা উচিত। যদি আপনার প্রশ্নটি কলব্যাক ইন্টারফেসটি ডিজাইন করার বিষয়ে হয় তবে এর উত্তর দেওয়ার জন্য এখানে পর্যাপ্ত তথ্য নেই near আপনার কলব্যাকের প্রাপক কী করতে চান? প্রাপকের কাছে আপনাকে কী তথ্য প্রেরণ করা দরকার? কলটির ফলাফল হিসাবে প্রাপককে আপনার কাছে কী তথ্য পাঠানো উচিত?
পিট বেকার

উত্তর:


171

সংক্ষেপে,std::function আপনার না করার কারণ না থাকলে ব্যবহার করুন।

ফাংশন পয়েন্টারগুলির কিছু প্রসঙ্গ ক্যাপচার করতে না পারার অসুবিধা রয়েছে । আপনি উদাহরণস্বরূপ একটি কলব্যাক হিসাবে ল্যাম্বডা ফাংশনটি পাস করতে সক্ষম হবেন না যা কিছু প্রসঙ্গের ভেরিয়েবলকে ক্যাপচার করে (তবে এটি কোনওটি ক্যাপচার না করলে এটি কাজ করবে)। কোনও অবজেক্টের সদস্যকে পরিবর্তনশীল (যেমন অ-স্থিতিশীল) কল করাও সম্ভব নয়, যেহেতু বস্তু ( this-পয়েন্টার) ক্যাপচার করা দরকার। (1)

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

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

টেমপ্লেট প্যারামিটার সহ সংস্করণের উদাহরণ ( প্রি-সি ++ এর &পরিবর্তে লিখুন &&):

template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
    ...
    callback(...);
    ...
}

আপনি যেমন নীচের টেবিলটিতে দেখতে পাচ্ছেন, তাদের সবারই রয়েছে তাদের সুবিধা এবং অসুবিধা:

+-------------------+--------------+---------------+----------------+
|                   | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture       |    no(1)     |      yes      |       yes      |
| context variables |              |               |                |
+-------------------+--------------+---------------+----------------+
| no call overhead  |     yes      |       no      |       yes      |
| (see comments)    |              |               |                |
+-------------------+--------------+---------------+----------------+
| can be inlined    |      no      |       no      |       yes      |
| (see comments)    |              |               |                |
+-------------------+--------------+---------------+----------------+
| can be stored     |     yes      |      yes      |      no(2)     |
| in class member   |              |               |                |
+-------------------+--------------+---------------+----------------+
| can be implemented|     yes      |      yes      |       no       |
| outside of header |              |               |                |
+-------------------+--------------+---------------+----------------+
| supported without |     yes      |     no(3)     |       yes      |
| C++11 standard    |              |               |                |
+-------------------+--------------+---------------+----------------+
| nicely readable   |      no      |      yes      |      (yes)     |
| (my opinion)      | (ugly type)  |               |                |
+-------------------+--------------+---------------+----------------+

(1) এই সীমাবদ্ধতা কাটিয়ে ওঠার জন্য ওয়ার্কআরউন্ডগুলি বিদ্যমান রয়েছে, উদাহরণস্বরূপ আপনার (বাহ্যিক) ফাংশনে আরও পরামিতি হিসাবে অতিরিক্ত ডেটা পাস করা: myFunction(..., callback, data)কল করবে callback(data)। এটি সি-স্টাইলের "আর্গুমেন্টগুলির সাথে কলব্যাক", যা সি ++ এ সম্ভব (এবং যেভাবে ডাব্লুআইএন 32 এপিআইতে ব্যবহৃত হয়েছিল) তবে তা এড়ানো উচিত কারণ আমাদের সি ++ তে আরও ভাল বিকল্প রয়েছে।

(২) যদি না আমরা কোনও ক্লাস টেম্পলেট, অর্থাৎ যে ক্লাসে আপনি ফাংশনটি সঞ্চয় করে থাকি সে সম্পর্কে কথা বলি। তবে এর অর্থ হ'ল ক্লায়েন্টের পক্ষ থেকে ফাংশনের ধরণটি সেই বস্তুর ধরণের সিদ্ধান্ত নেয় যা কলব্যাক সংরক্ষণ করে, যা প্রকৃত ব্যবহারের ক্ষেত্রে প্রায়শই কোনও বিকল্প নয়।

(3) প্রাক-সি ++ 11 এর জন্য ব্যবহার করুন boost::function


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

1
@ Tohecz আমি এখন উল্লেখ করছি যদি এর জন্য সি ++ 11 প্রয়োজন হয় বা না।
লীমস

1
@ ইয়াক ওহ, অবশ্যই ভুলে গেছেন! ধন্যবাদ, ধন্যবাদ।
লীমস

1
@ মুভিংডাক অবশ্যই এটি বাস্তবায়নের উপর নির্ভর করে। তবে যদি আমি সঠিকভাবে মনে রাখি, টাইপ মোছা কাজ করার কারণে কীভাবে আরও একটি ইন্ডিरेশন হয়? তবে এখন আমি এটি সম্পর্কে আবারও ভাবছি, আমি অনুমান করি আপনি যদি এটির জন্য ফাংশন পয়েন্টার বা ক্যাপচার-কম ল্যাম্বডাস অর্পণ করেন তবে ... (সাধারণ অপ্টিমাইজেশন হিসাবে)
লিমস

1
@ লেলিজ: ঠিক আছে, ফাংশন পয়েন্টার বা ক্যাপচারহীন ল্যাম্বডাসের জন্য এটির সি- ফানক-পিটিআর মতো ওভারহেড থাকা উচিত । যা এখনও একটি পাইপলাইন স্টল + তুচ্ছভাবে অন্তর্ভুক্ত নয়।
মাকিং হাঁস

25

void (*callbackFunc)(int); কোনও সি স্টাইলের কলব্যাক ফাংশন হতে পারে তবে এটি দরিদ্র ডিজাইনের একটি মারাত্মকভাবে ব্যবহারযোগ্য।

একটি ভাল ডিজাইন করা সি স্টাইলের কলব্যাক দেখতে দেখতে void (*callbackFunc)(void*, int);- এতে কোডটির void*অনুমতি দেওয়া আছে যা কলব্যাকটি ফাংশন ছাড়িয়ে রাজ্য বজায় রাখতে পারে। এটি না করা কলকারীকে বিশ্বব্যাপী রাষ্ট্র সঞ্চয় করতে বাধ্য করে, যা অসম্পূর্ণ।

std::function< int(int) >int(*)(void*, int)বেশিরভাগ বাস্তবায়নে ডাকে চেয়ে কিছুটা ব্যয়বহুল হয়ে যায়। কিছু সংকলকগুলির জন্য ইনলাইন করা এটি আরও শক্ত। এমন std::functionক্লোন বাস্তবায়ন রয়েছে যা প্রতিদ্বন্দ্বী ফাংশন পয়েন্টার আহ্বান ওভারহেডগুলি ('দ্রুততম সম্ভাব্য প্রতিনিধি' ইত্যাদি দেখুন) যা লাইব্রেরিতে প্রবেশ করতে পারে।

এখন, কলব্যাক সিস্টেমের ক্লায়েন্টদের প্রায়শই রিসোর্স সেট আপ করতে হবে এবং কলব্যাক তৈরি এবং সরিয়ে ফেলা হওয়ার পরে সেগুলি নিষ্পত্তি করতে হবে এবং কলব্যাকের জীবনকাল সম্পর্কে সচেতন হতে হবে। void(*callback)(void*, int)এটি সরবরাহ করে না

কখনও কখনও কোড স্ট্রাকচারের মাধ্যমে (কলব্যাকের আয়ু সীমিত থাকে) বা অন্যান্য প্রক্রিয়াগুলির মাধ্যমে (অনিবন্ধিত কলব্যাক এবং এর মতো) এর মাধ্যমে উপলব্ধ।

std::function সীমিত জীবনকাল পরিচালনার জন্য একটি উপায় সরবরাহ করে (ভুলে গেলে বস্তুর শেষ কপিটি চলে যায়)।

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

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

নোট করুন যে আপনি উপযুক্ত কলব্যাক লাইফটাইম ম্যানেজমেন্ট অবকাঠামো রয়েছে তা ধরে নিয়ে এমন স্টাইল কলব্যাককে std::function<int(int)>রূপান্তর করে এমন মোড়ক লিখতে পারেন int(void*,int)। সুতরাং যে কোনও সি-স্টাইলের কলব্যাক লাইফটাইম ম্যানেজমেন্ট সিস্টেমের জন্য ধূমপান পরীক্ষা হিসাবে, আমি নিশ্চিত করব যে কোনও std::functionকাজকে যুক্তিসঙ্গতভাবে গুটিয়ে রাখা উচিত।


1
কোথা void*থেকে এলো? আপনি কেন ফাংশন ছাড়িয়ে রাষ্ট্র বজায় রাখতে চান? একটি ফাংশনটিতে তার প্রয়োজনীয় সমস্ত কোড, সমস্ত কার্যকারিতা থাকা উচিত, আপনি কেবল এটি পছন্দসই যুক্তিগুলি পাস করে কিছু পরিবর্তন এবং ফিরিয়ে আনতে পারেন। আপনার যদি কিছু বাহ্যিক অবস্থার প্রয়োজন হয় তবে কোনও ফাংশনপিআর বা কলব্যাক কেন সেই লাগেজ বহন করবে? আমি মনে করি যে কলব্যাক অহেতুক জটিল।
নিকস

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

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

@ নিক-এলজেড সদস্য ফাংশনগুলি কার্যকর নয়। কার্যসমূহের কোনও (রানটাইম) অবস্থা নেই। কলব্যাকগুলি void*রানটাইম রাজ্যের সংক্রমণকে অনুমতি দেয়। একটি void*এবং একটি void*আর্গুমেন্ট সহ একটি ফাংশন পয়েন্টার একটি বস্তুর একটি সদস্য ফাংশন কল অনুকরণ করতে পারে। দুঃখিত, আমি এমন একটি সংস্থান সম্পর্কে জানি না যা "সি কলব্যাক মেকানিজম 101 ডিজাইনিং" এর মধ্য দিয়ে চলে।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

হ্যাঁ, আমি এটিই বলছিলাম। রানটাইম স্থিতি হ'ল মূলত সেই বস্তুর ঠিকানা বলা হয় (কারণ এটি রানের মধ্যে পরিবর্তিত হয়)। এটি এখনও প্রায় this। যে আমি বোঝানো কি. ঠিক আছে, যাইহোক ধন্যবাদ।
নিকোস

17

std::functionযথেচ্ছ কলযোগ্য বস্তু সঞ্চয় করতে ব্যবহার করুন । এটি ব্যবহারকারীকে কলব্যাকের জন্য প্রাসঙ্গিক যা প্রয়োজন তা সরবরাহ করতে দেয়; একটি সরল ফাংশন পয়েন্টার না।

যদি আপনার কোনও কারণে প্লেইন ফাংশন পয়েন্টার ব্যবহার করার প্রয়োজন হয় (সম্ভবত আপনি সি-সামঞ্জস্যপূর্ণ এপিআই চান) তবে আপনার পক্ষে void * user_contextযুক্তি যুক্ত করা উচিত যাতে এটির অ্যাক্সেসের জন্য কমপক্ষে সম্ভব (অসুবিধে হলেও) যা সরাসরি প্রবেশ করা হয়নি access ফাংশন।


এখানে পি টাইপ কি? এটি একটি স্টাড :: ফাংশন টাইপ হবে? অকার্যকর চ () {}; অটো পি = এফ; পি ();
শ্রী

14

এড়ানোর একমাত্র কারণ std::function হ'ল লেগ্যাসি সংকলকগুলির সমর্থন যা এই টেমপ্লেটের জন্য সমর্থন সমর্থন করে না, যা সি ++ 11 এ চালু করা হয়েছে।

প্রাক-সি ++ ১১ টি ভাষা সমর্থন করা যদি প্রয়োজন হয় না, ব্যবহার std::functionকরে আপনার কলারকে কলব্যাক বাস্তবায়নে আরও পছন্দ দেয়, এটি "সাধারণ" ফাংশন পয়েন্টারের তুলনায় আরও ভাল বিকল্প হিসাবে তৈরি করে। এটি আপনার এপিআই-র ব্যবহারকারীদের আরও পছন্দ দেয়, যখন আপনার কোডটির জন্য কলব্যাক সম্পাদন করে তাদের বাস্তবায়নের সুনির্দিষ্ট বিবরণ বের করে।


1

std::function কিছু ক্ষেত্রে কোডটিতে ভিএমটি আনতে পারে, যা কার্য সম্পাদনে কিছুটা প্রভাব ফেলে।


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