আমি গত চার বছর ধরে এই প্রশ্নটি সম্পর্কে বেশ খানিকটা চিন্তা করেছি। আমি এই সিদ্ধান্তে পৌঁছেছি যে push_back
বনাম সম্পর্কে সর্বাধিক ব্যাখ্যা emplace_back
সম্পূর্ণ চিত্রকে মিস করে।
গত বছর, আমি সি ++ এ সি ++ তে টাইপ ছাড়ের উপর একটি উপস্থাপনা দিয়েছিলাম । আমি 13: push_back
49- emplace_back
এ বনাম সম্পর্কে কথা বলতে শুরু করি , তবে দরকারী তথ্য রয়েছে যা এর আগে কিছু সহায়ক প্রমাণ সরবরাহ করে।
আসল প্রাথমিক তফাতটি অন্তর্নিহিত বনাম সুস্পষ্ট নির্মাণকারীদের সাথে করতে হবে। কেস আছে যেখানে আমরা একটি একক যুক্তি যে আমরা পাস করতে চান বিবেচনা করুন push_back
বা emplace_back
।
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
আপনার অনুকূলিতকরণ সংকলকটি এতে হাত পেতে পরে, উত্পন্ন কোডের ক্ষেত্রে এই দুটি বিবৃতিতে কোনও পার্থক্য নেই। Wisdomতিহ্যগত বুদ্ধি হ'ল push_back
একটি অস্থায়ী অবজেক্ট তৈরি করবে, যা পরে স্থানান্তরিত হবে v
যেখানে emplace_back
যুক্তিটি প্রেরণ করবে এবং কোনও অনুলিপি বা চালনা ছাড়াই সরাসরি জায়গায় এটি নির্মাণ করবে। স্ট্যান্ডার্ড লাইব্রেরিতে লিখিতভাবে কোডের উপর ভিত্তি করে এটি সত্য হতে পারে, তবে এটি ভুল ধারণা তৈরি করে যে অনুকূলিতকরণ সংকলকের কাজটি আপনার লেখা কোডটি উত্পন্ন করা to অপ্টিমাইজ করা সংকলকের কাজটি আসলে আপনি যে কোডটি লিখেছিলেন তা উত্পন্ন করে যদি আপনি প্ল্যাটফর্ম-নির্দিষ্ট অপ্টিমাইজেশনে বিশেষজ্ঞ হন এবং রক্ষণাবেক্ষণের বিষয়ে যত্ন নেন না, কেবল পারফরম্যান্স।
এই দুটি বিবরণের মধ্যে প্রকৃত পার্থক্য হ'ল আরও শক্তিশালী emplace_back
সেখানে যে কোনও ধরণের কনস্ট্রাক্টরকে কল করবেন, যেখানে আরও সতর্কতা push_back
কেবল সেই কনস্ট্রাক্টরকে কল করবে যা অন্তর্নিহিত। অন্তর্ভুক্ত নির্মাতারা নিরাপদ থাকার কথা। আপনি যদি একটি U
থেকে স্পষ্টতই একটি নির্মাণ করতে পারেন T
, আপনি বলছেন যে কোনও ক্ষতি ছাড়াই U
সমস্ত তথ্য ধরে রাখতে পারে T
। এটিকে পাস করা মোটামুটি কোনও পরিস্থিতিতে নিরাপদ এবং T
আপনি যদি এটির U
পরিবর্তে তৈরি করেন তবে কেউ আপত্তি করবে না। একটি অন্তর্নিহিত কন্সট্রাকটর একটি ভাল উদাহরণ থেকে রুপান্তরের হয় std::uint32_t
থেকে std::uint64_t
। একটি অন্তর্নিহিত রূপান্তর একটি খারাপ উদাহরণ double
থেকে std::uint8_t
।
আমরা আমাদের প্রোগ্রামিংয়ে সতর্ক হতে চাই। আমরা শক্তিশালী বৈশিষ্ট্যগুলি ব্যবহার করতে চাই না কারণ বৈশিষ্ট্যটি যত বেশি শক্তিশালী, দুর্ঘটনাক্রমে ভুল বা অপ্রত্যাশিতভাবে কিছু করা সহজ। আপনি যদি সুস্পষ্ট নির্মাতাদের কল করতে চান, তবে আপনার পাওয়ারটি দরকার emplace_back
। আপনি যদি কেবল অন্তর্নিহিত নির্মাতাদের কল করতে চান তবে এর সুরক্ষার সাথে লেগে থাকুন push_back
।
একটি উদাহরণ
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
এর একটি সুস্পষ্ট নির্মাতা রয়েছে T *
। কারণ emplace_back
স্বতন্ত্র নির্মাতাদের কল করতে পারে, একটি নন-মালিকানা পয়েন্টারটি ঠিকঠাক সংকলন করে। যাইহোক, যখন v
সুযোগের বাইরে চলে যায় , তখন ডেস্ট্রাক্টর delete
সেই পয়েন্টারে কল করার চেষ্টা করবে , যা বরাদ্দ করা হয়নি new
কারণ এটি কেবল স্ট্যাক অবজেক্ট। এটি অনির্ধারিত আচরণের দিকে পরিচালিত করে।
এটি কেবল উদ্ভাবিত কোড নয়। এটি একটি সত্যই প্রোডাকশন বাগ ছিল যার মুখোমুখি হয়েছিল। কোডটি ছিল std::vector<T *>
, তবে এতে সামগ্রীর মালিকানা ছিল । সি ++ 11 এ মাইগ্রেশনের অংশ হিসাবে, আমি ভেক্টরটির স্মৃতিশক্তির মালিকানা তা চিহ্নিত T *
করতে সঠিকভাবে পরিবর্তন করেছি std::unique_ptr<T>
। যাইহোক, আমি 2012 সালে আমার বোঝার বন্ধ এই পরিবর্তনগুলি ভিত্তিবিন্দু ছিল, যা সময় আমি ভেবেছিলাম "emplace_back সবকিছু push_back কি করতে পারেন না এবং আরো, তবে কেন আমি কখনো push_back ব্যবহার করেন?", তাই আমিও পরিবর্তিত push_back
করতে emplace_back
।
যদি আমি পরিবর্তে কোডটি নিরাপদ হিসাবে ব্যবহার করে ছেড়ে চলে যাই, তবে আমি push_back
তাত্ক্ষণিকভাবে এই দীর্ঘস্থায়ী বাগটিটি ধরতে পারতাম এবং এটি সি ++ 11 এ আপগ্রেড করার সাফল্য হিসাবে দেখা হত। পরিবর্তে, আমি বাগটি মাস্ক করেছিলাম এবং কয়েক মাস পরে এটি খুঁজে পেলাম না।