আমি গত চার বছর ধরে এই প্রশ্নটি সম্পর্কে বেশ খানিকটা চিন্তা করেছি। আমি এই সিদ্ধান্তে পৌঁছেছি যে push_backবনাম সম্পর্কে সর্বাধিক ব্যাখ্যা emplace_backসম্পূর্ণ চিত্রকে মিস করে।
গত বছর, আমি সি ++ এ সি ++ তে টাইপ ছাড়ের উপর একটি উপস্থাপনা দিয়েছিলাম । আমি 13: push_back49- 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 এ আপগ্রেড করার সাফল্য হিসাবে দেখা হত। পরিবর্তে, আমি বাগটি মাস্ক করেছিলাম এবং কয়েক মাস পরে এটি খুঁজে পেলাম না।