C++ কৌশল লিখার আগে কিছু মৌলিক জ্ঞান থাকা দরকার, কঙ্গো-বিদ্বেষী দক্ষতা না, অন্তত এই নিয়মগুলো জানা দরকার। নিম্নে পুনর্নির্দেশ করা হয়েছেঃ
যদি কেউ নিজেকে একজন প্রোগ্রামার বলে দাবি করে এবং মেমরি সম্পর্কে কিছুই জানে না, তবে আমি আপনাকে বলতে পারি যে সে অবশ্যই গর্বিত। সি বা সি ++ এ প্রোগ্রাম লেখার জন্য, মেমরিতে আরও বেশি মনোযোগ দেওয়া দরকার, কারণ কেবলমাত্র মেমরির বরাদ্দ সঠিক কিনা তা সরাসরি প্রোগ্রামের দক্ষতা এবং পারফরম্যান্সের উপর প্রভাব ফেলে না, তবে মূলত, আমরা যখন মেমরিটি পরিচালনা করি তখন সমস্যাগুলি ঘটতে পারে, এবং অনেক সময়, এই সমস্যাগুলি সহজেই সনাক্ত করা যায় না, যেমন মেমরির ফাঁস, যেমন ডাইভার হ্যাং পয়েন্ট। লেখক আজ এখানে এই সমস্যাগুলি কীভাবে এড়ানো যায় তা নিয়ে আলোচনা করতে আসেন না, তবে সি ++ মেমরি অবজেক্টগুলিকে অন্য দৃষ্টিকোণ থেকে জানতে চান।
আমরা জানি যে, C++ মেমরিকে তিনটি লজিক্যাল অঞ্চলে ভাগ করে দেয়: স্ট্যাক, ক্যাচ এবং স্ট্যাটিক স্টোরেজ অঞ্চল। যেহেতু এটি সত্য, আমি সেগুলির মধ্যে থাকা বস্তুগুলিকে স্ট্যাক অবজেক্ট, ক্যাচ অবজেক্ট এবং স্ট্যাটিক অবজেক্ট বলেছি। তাহলে এই বিভিন্ন মেমরি অবজেক্টগুলির মধ্যে পার্থক্য কী? স্ট্যাক অবজেক্ট এবং ক্যাচ অবজেক্টের কী কী সুবিধা এবং অসুবিধা রয়েছে? কীভাবে স্ট্যাক অবজেক্ট বা ক্যাচ অবজেক্ট তৈরি করা নিষিদ্ধ? এগুলি আজকের বিষয়।
১ মৌলিক ধারণা
প্রথমে দেখা যাক , যা সাধারণত স্থানীয় পরিবর্তনশীল বা বস্তু সংরক্ষণের জন্য ব্যবহৃত হয়, যেমন আমরা ফাংশন সংজ্ঞায়িত একটি অনুরূপ বিবৃতি দিয়ে ঘোষণা করিঃ
Type stack_object ;
stack_object একটি স্ট্যাক অবজেক্ট, যার জীবনকাল একটি সংজ্ঞা পয়েন্ট থেকে শুরু হয় এবং যখন এটির ফাংশনটি ফিরে আসে তখন জীবন শেষ হয়।
তদুপরি, প্রায় সব অস্থায়ী অবজেক্টই কুলুঙ্গি অবজেক্ট। উদাহরণস্বরূপ, নিম্নলিখিত ফাংশন সংজ্ঞাঃ
Type fun(Type object);
এই ফাংশনটি কমপক্ষে দুটি অস্থায়ী অবজেক্ট তৈরি করে, প্রথমত, একটি মান-পরিবর্তিত প্যারামিটার, তাই একটি অস্থায়ী অবজেক্ট object_copy1 তৈরি করতে কপি কনস্ট্রাকশন ফাংশনটি ডাকা হয়। ফাংশনের অভ্যন্তরে ব্যবহৃত অবজেক্টটি অবজেক্ট নয়, তবে অবজেক্ট_কপি 1 ব্যবহার করা হয়, স্বাভাবিকভাবেই, অবজেক্ট_কপি 1 একটি ক্যাচ অবজেক্ট, যা ফাংশনটি ফেরত দেওয়ার সময় ছেড়ে দেওয়া হয়; এবং এই ফাংশনটিও একটি মান ফেরত দেওয়া হয়, যখন ফাংশনটি ফেরত দেওয়া হয়, যদি আমরা রিটার্ন মান অপ্টিমাইজেশন ((এনআরভি) বিবেচনা না করি, তবে একটি অস্থায়ী অবজেক্ট object_copy2ও তৈরি করা হয়, যা ফাংশনটি ফেরত দেওয়ার পরে একটি সময়ের মধ্যে ছেড়ে দেওয়া হয়। উদাহরণস্বরূপ, একটি ফাংশন নিম্নলিখিত কোড রয়েছেঃ
Type tt ,result ; //生成两个栈对象
tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
উপরের দ্বিতীয় বিবৃতিটির বাস্তবায়ন হল, প্রথমে ফাংশনটি একটি অস্থায়ী অবজেক্ট object_copy2 উৎপন্ন করে যখন এটি ফাংশনটি ফেরত দেয়, এবং তারপরে এটি কার্যকর করার জন্য মান নির্ধারণকারীকে কল করে
tt = object_copy2 ; //调用赋值运算符
আপনি কি দেখেছেন? কম্পাইলার আমাদের অজ্ঞাতসারে আমাদের জন্য এতগুলি অস্থায়ী বস্তু তৈরি করেছে, এবং এই অস্থায়ী বস্তুগুলি তৈরি করার জন্য সময় এবং স্থান ব্যয় হতে পারে, তাই আপনি বুঝতে পারেন যে কেন ফাংশন প্যারামিটারগুলি মান অনুসারে পরিবর্তে কনস্ট রেফারেন্সের মাধ্যমে প্রেরণ করা ভাল।
এরপরে, স্ট্যাকের দিকে তাকান। স্ট্যাক, যাকে ফ্রি স্টোরেজ এলাকাও বলা হয়, এটি প্রোগ্রামের কার্য সম্পাদনের সময় গতিশীলভাবে বরাদ্দ করা হয়, সুতরাং এর সর্বাধিক বৈশিষ্ট্যটি গতিশীলতা। সি ++ এ, সমস্ত স্ট্যাক অবজেক্টের তৈরি এবং ধ্বংস প্রোগ্রামারদের দ্বারা করা হয়, সুতরাং, যদি এটি খারাপভাবে পরিচালনা করা হয় তবে মেমরির সমস্যা দেখা দেয়। যদি স্ট্যাক অবজেক্ট বরাদ্দ করা হয়, তবে মুক্তি দেওয়া ভুলে যাওয়া হয়, তবে মেমরি লিক হয়। যদি অবজেক্টটি মুক্তি দেওয়া হয় তবে সংশ্লিষ্ট পয়েন্টারটি NULL হিসাবে সেট না করা হয়, তবে এই নির্দেশকটি তথাকথিত ঝুলন্ত পয়েন্টার, আবার এই নির্দেশকটি ব্যবহার করলে অবৈধ অ্যাক্সেস দেখা দেয়, গুরুতর ক্ষেত্রে প্রোগ্রামটি ক্র্যাশ হতে পারে।
সুতরাং, C++ এ কিভাবে স্ট্যাক অবজেক্ট বণ্টন করা হয়? একমাত্র উপায় হল new ব্যবহার করা (অবশ্যই, C-র স্ট্যাক মেমরির জন্য malloc-class নির্দেশাবলী ব্যবহার করা যেতে পারে), যেহেতু new ব্যবহার করে, স্ট্যাকের মধ্যে একটি মেমরি বণ্টন করা হয় এবং স্ট্যাক অবজেক্টের দিকে নির্দেশ করে একটি পয়েন্টার ফেরত দেওয়া হয়।
আবার স্ট্যাটিক স্টোরেজ দেখুন। সমস্ত স্ট্যাটিক অবজেক্ট, গ্লোবাল অবজেক্টগুলি স্ট্যাটিক স্টোরেজ অঞ্চলে বরাদ্দ করা হয়। গ্লোবাল অবজেক্টের ক্ষেত্রে, main () ফাংশনটি কার্যকর হওয়ার আগে এটি বরাদ্দ করা হয়। প্রকৃতপক্ষে, main () ফাংশনে প্রদর্শিত কোডটি কার্যকর হওয়ার আগে, সংকলক দ্বারা উত্পন্ন একটি _main () ফাংশন ডাকা হবে, এবং _main () ফাংশনটি সমস্ত গ্লোবাল অবজেক্টের কাঠামোগত এবং প্রাথমিকীকরণের কাজ করবে। main () ফাংশনটি শেষ হওয়ার আগে, সমস্ত গ্লোবাল অবজেক্টগুলি মুক্ত করার জন্য সংকলক দ্বারা উত্পন্ন একটি এক্সট ফাংশন ডাকা হবে। যেমন নিম্নলিখিত কোডটিঃ
void main(void)
{
... // 显式代码
}
// 实际上转化为这样:
void main(void)
{
_main(); //隐式代码,由编译器产生,用以构造所有全局对象
... // 显式代码
...
exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
}
সুতরাং, এটি জেনে, আমরা কিছু কৌশল অবলম্বন করতে পারি, যেমন, ধরুন আমরা main () ফাংশনটি কার্যকর করার আগে কিছু প্রস্তুতিমূলক কাজ করতে চাই, তবে আমরা এই প্রস্তুতিগুলি একটি কাস্টমাইজড গ্লোবাল অবজেক্টের কনস্ট্রাকশন ফাংশনে লিখতে পারি, যাতে main () ফাংশনের এক্সপোজার কোডটি কার্যকর হওয়ার আগে, এই গ্লোবাল অবজেক্টের কনস্ট্রাকশন ফাংশনটি কল করা হয় এবং প্রত্যাশিত পদক্ষেপগুলি সম্পাদন করে, যা আমাদের উদ্দেশ্য অর্জন করে। আমরা স্ট্যাটিক স্টোরেজ অঞ্চলে গ্লোবাল অবজেক্ট সম্পর্কে কথা বলেছি, তবে স্থানীয়ভাবে স্ট্যাটিক অবজেক্টগুলি কী? স্থানীয়ভাবে স্ট্যাটিক অবজেক্টগুলিও সাধারণত ফাংশনে সংজ্ঞায়িত হয়, যেমন প্যাটার্ন অবজেক্টগুলি, তবে এর সামনে একাধিক স্ট্যাটিক কীওয়ার্ড রয়েছে। স্থানীয়ভাবে স্ট্যাটিক অবজেক্টের জীবনকাল হল ফাংশনটি প্রথমবার কল করা হয়, বা আরও সঠিকভাবে বলতে গেলে, যখন এই স্থির অবজেক্টটি প্রথমবার স্ট্যাটিক কোডটি ঘোষণা করে, তখন পুরো প্রোগ্রামটি ধ্বংস
একটি স্ট্যাটিক অবজেক্ট হলো একটি ক্লাসের একটি স্ট্যাটিক সদস্য। এই পরিস্থিতি বিবেচনা করার সময়, আরো জটিল কিছু বিষয় জড়িত।
প্রথম সমস্যাটি হল ক্লাসের স্ট্যাটিক সদস্য বস্তুর জীবনকাল, ক্লাসের স্ট্যাটিক সদস্য বস্তুটি প্রথম শ্রেণীর বস্তুর উত্পাদনের সাথে উত্পাদিত হয় এবং পুরো প্রক্রিয়া শেষে মারা যায়। অর্থাৎ, এমন একটি পরিস্থিতি বিদ্যমান, যেখানে আমরা একটি ক্লাস সংজ্ঞায়িত করি, যার মধ্যে একটি স্ট্যাটিক অবজেক্ট সদস্য হিসাবে রয়েছে, তবে প্রোগ্রামটি চালানোর সময়, যদি আমরা এই শ্রেণীর কোনও অবজেক্ট তৈরি না করি, তবে এই শ্রেণীর অন্তর্ভুক্ত স্ট্যাটিক অবজেক্ট তৈরি হবে না। এছাড়াও, যদি একাধিক শ্রেণীর অবজেক্ট তৈরি করা হয় তবে এই সমস্ত অবজেক্টগুলি সেই স্ট্যাটিক অবজেক্টের সদস্যকে ভাগ করে নেয়।
দ্বিতীয়ত, যখন নিম্নলিখিত ঘটনা ঘটেঃ
class Base
{
public:
static Type s_object ;
}
class Derived1 : public Base / / 公共继承
{
... // other data
}
class Derived2 : public Base / / 公共继承
{
... // other data
}
Base example ;
Derivde1 example1 ;
Derivde2 example2 ;
example.s_object = …… ;
example1.s_object = …… ;
example2.s_object = …… ;
উপরের তিনটি বাক্যে কালো ট্যাগ দেওয়া আছে, তারা কি একই s_object এর সাথে যোগাযোগ করছে? উত্তরটি হল হ্যাঁ, তারা একই বস্তুর সাথে যোগাযোগ করছে, যা সত্য বলে মনে হচ্ছে না, তাই না? কিন্তু এটি সত্য, আপনি নিজের জন্য একটি সহজ কোড লিখতে পারেন যাচাই করতে। আমি যা করতে যাচ্ছি তা ব্যাখ্যা করতে হবে কেন এটি ঘটে। আমরা জানি যে যখন একটি শ্রেণী যেমন Derived1 অন্য একটি শ্রেণী যেমন Base থেকে উত্তরাধিকারসূত্রে প্রাপ্ত হয়, তখন আমরা দেখতে পারি যে একটি Derived1 অবজেক্টের মধ্যে একটি Base-type অবজেক্ট রয়েছে, এটি একটি subobject। একটি Derived1 অবজেক্টের মোটামুটি মেমরি বিন্যাস নিম্নরূপঃ
আসুন চিন্তা করি, যখন আমরা একটি Derived1-type অবজেক্টকে একটি ফাংশনকে দিই যেটি non-reference Base-type arguments গ্রহণ করে, তখন কিভাবে একটি cutoff ঘটবে? বিশ্বাস করুন এখন আপনি জানেন যে, এটি কেবলমাত্র Derived1-type অবজেক্টের subobject কে বের করে নিয়েছে, এবং Derived1 এর অন্যান্য ডেটা সদস্যদের উপেক্ষা করে, এবং এই subobject কে ফাংশন (অবশ্যই, ফাংশনটি এই subobject এর একটি কপি ব্যবহার করে) ।
সকল অবজেক্টের মধ্যে একটি Subobject থাকে যার নাম হচ্ছে Base (এটি হল একটি পলিথিনিক কী যেখানে Base টাইপ পয়েন্টার ব্যবহার করে একটি Derived1 অবজেক্ট নির্দেশ করা যায়) এবং সব Subobject এবং সকল Base টাইপ অবজেক্ট একই s_object অবজেক্ট ব্যবহার করে। স্বাভাবিকভাবেই, Base থেকে প্রাপ্ত সকল অবজেক্টের মধ্যে একটি s_object অবজেক্ট ব্যবহার করা হয়।
২ তিনটি মেমোরি অবজেক্টের তুলনা
কুলুঙ্গি অবজেক্টের সুবিধা হ’ল এটি যথাযথ সময়ে স্বয়ংক্রিয়ভাবে উত্পন্ন হয় এবং যথাযথ সময়ে স্বয়ংক্রিয়ভাবে ধ্বংস হয়, প্রোগ্রামারদের উদ্বেগের প্রয়োজন হয় না; এবং কুলুঙ্গি অবজেক্টের তৈরির গতি সাধারণত স্ট্যাক অবজেক্টের চেয়ে দ্রুত হয়, কারণ স্ট্যাক অবজেক্টগুলি বিতরণ করার সময়, অপারেটর নতুন অপারেশনটি কল করা হয়, অপারেটর নতুন মেমরি স্পেস অনুসন্ধান অ্যালগরিদম ব্যবহার করে, যা অনুসন্ধান প্রক্রিয়াটি অনেক সময় ব্যয়বহুল হতে পারে, কুলুঙ্গি অবজেক্ট উত্পন্ন করার ক্ষেত্রে এটি এতটা ঝামেলা নয়, এটি কেবল কুলুঙ্গি কয়েস্টকে সরানো প্রয়োজন। তবে এটি লক্ষ করা উচিত যে সাধারণত কুলুঙ্গি স্পেসটি তুলনামূলকভাবে ছোট, সাধারণত 1MB থেকে 2MB, তাই আকারের তুলনামূলকভাবে বড় অবজেক্টগুলি কুলুঙ্গিতে বিতরণ করার জন্য উপযুক্ত নয়। বিশেষত পুনরাবৃত্তিম ফাংশনগুলিতে কুলুঙ্গি ব্যবহার না করা ভাল, কারণ পুনরাবৃত্তিক কলের গভীরতা
স্ট্যাক অবজেক্ট, যখন এটি তৈরি করা হয় এবং যখন এটি ধ্বংস করা হয় তখন প্রোগ্রামারদের দ্বারা সুনির্দিষ্টভাবে সংজ্ঞায়িত করা হয়, অর্থাৎ প্রোগ্রামারদের স্ট্যাক অবজেক্টের জীবনের উপর সম্পূর্ণ নিয়ন্ত্রণ থাকে। আমাদের প্রায়শই এমন একটি অবজেক্টের প্রয়োজন হয়, উদাহরণস্বরূপ, আমরা এমন একটি অবজেক্ট তৈরি করতে চাই যা একাধিক ফাংশন দ্বারা অ্যাক্সেস করা যায়, তবে এটিকে সর্বজনীন হতে চাই না, তবে এই সময়ে একটি স্ট্যাক অবজেক্ট তৈরি করা নিঃসন্দেহে একটি ভাল বিকল্প, তারপরে প্রতিটি ফাংশনের মধ্যে এই স্ট্যাক অবজেক্টের কয়েন্টার প্রেরণ করা যায়, যাতে এই অবজেক্টের ভাগ করে নেওয়া যায়। এছাড়াও, স্ট্যাকের ধারণক্ষমতা খালি জায়গার তুলনায় অনেক বেশি। প্রকৃতপক্ষে, যখন শারীরিক মেমরি যথেষ্ট হয় না, তখন যদি নতুন স্ট্যাক অবজেক্ট তৈরি করার প্রয়োজন হয় তবে সাধারণত চলার সময় ত্রুটি হয় না, তবে সিস্টেম ভার্চুয়াল মেমরি ব্যবহার করে প্রকৃত শারীরিক মেমরি প্রসারিত করে।
এখন স্ট্যাটিক অবজেক্টের দিকে তাকান।
প্রথমত, গ্লোবাল অবজেক্টস। গ্লোবাল অবজেক্টস ক্লাস এবং ফাংশনগুলির মধ্যে যোগাযোগের সবচেয়ে সহজ উপায় সরবরাহ করে, যদিও এটি মার্জিত নয়। সাধারণভাবে, সম্পূর্ণরূপে অবজেক্ট-ভিত্তিক ভাষায় গ্লোবাল অবজেক্টস নেই, যেমন সি #, কারণ গ্লোবাল অবজেক্টস মানে অনিরাপদ এবং উচ্চ সংযোজন, এবং প্রোগ্রামের মধ্যে গ্লোবাল অবজেক্টের অত্যধিক ব্যবহার প্রোগ্রামের দৃness়তা, স্থিতিশীলতা, রক্ষণাবেক্ষণযোগ্যতা এবং বহুমুখিতা হ্রাস করে। C ++ও সম্পূর্ণরূপে গ্লোবাল অবজেক্টসকে সরিয়ে ফেলতে পারে, তবে শেষ পর্যন্ত এটি নয়, আমি মনে করি কারণগুলির মধ্যে একটি হ’ল সি-র সাথে সামঞ্জস্যপূর্ণ।
দ্বিতীয়টি হল ক্লাসের স্ট্যাটিক সদস্য, উপরে উল্লেখ করা হয়েছে যে বেস ক্লাস এবং তার ডেরিভেটিভ ক্লাসের সমস্ত অবজেক্ট এই স্ট্যাটিক সদস্য অবজেক্টটি ভাগ করে নেয়, তাই যখন এই ক্লাসগুলির মধ্যে বা এই শ্রেণীর অবজেক্টগুলির মধ্যে ডেটা ভাগ করা বা যোগাযোগের প্রয়োজন হয় তখন এই ধরণের স্ট্যাটিক সদস্যটি অবশ্যই একটি ভাল পছন্দ।
এরপরে রয়েছে স্ট্যাটিক স্থানীয় বস্তু, যা মূলত একটি ফাংশনকে বারবার কল করার সময় তার অন্তর্বর্তী অবস্থা সংরক্ষণের জন্য ব্যবহার করা হয়। এর মধ্যে একটি উল্লেখযোগ্য উদাহরণ হল একটি পুনরাবৃত্তিমূলক ফাংশন। আমরা সবাই জানি যে পুনরাবৃত্তিমূলক ফাংশনগুলি তাদের নিজস্ব ফাংশনকে কল করে। যদি পুনরাবৃত্তিমূলক ফাংশনগুলিতে একটি ননস্ট্যাটিক স্থানীয় বস্তু সংজ্ঞায়িত করা হয়, তবে পুনরাবৃত্তিমূলক সংখ্যাটি বেশ বড় হলে, ব্যয়ও বিশাল। এটি কারণ ননস্ট্যাটিক স্থানীয় বস্তুগুলি একটি ঘুড়ি বস্তু, প্রতিটি পুনরাবৃত্তিমূলক কল একবার, এই ধরনের একটি বস্তু উত্পন্ন করে, এবং প্রতিবার ফিরে, এই অবজেক্টটি মুক্তি দেয়, এবং এই ধরনের বস্তুগুলি কেবলমাত্র বর্তমান কল স্তরের মধ্যে সীমাবদ্ধ থাকে। আরও গভীর এম্বেড স্তর এবং আরও অগভীর স্তরের জন্য অদৃশ্য। প্রতিটি স্থানীয় বস্তুর নিজস্ব বস্তু এবং প্যারামিটার রয়েছে।
পুনরাবৃত্তিমূলক ফাংশন ডিজাইনে, স্ট্যাটিক অবজেক্টগুলিকে অ-স্ট্যাটিক স্থানীয় অবজেক্টের পরিবর্তে ব্যবহার করা যেতে পারে (অর্থাৎ, ক্যাশ অবজেক্ট), যা কেবল প্রতিবার পুনরাবৃত্তিমূলক কল এবং রিটার্নের সময় উত্পন্ন এবং মুক্ত অ-স্ট্যাটিক অবজেক্টের ব্যয় হ্রাস করে না, তবে স্ট্যাটিক অবজেক্টগুলি পুনরাবৃত্তিমূলক কলের মধ্যবর্তী অবস্থা সংরক্ষণ করতে পারে এবং বিভিন্ন কল স্তরের জন্য অ্যাক্সেসযোগ্য।
৩ ক্যালোরি ব্যবহার করে অপ্রত্যাশিত ফলন
আমরা আগেই বলেছি যে, একটি অ্যালগরিদম অবজেক্ট যথাসময়ে তৈরি করা হয় এবং যথাসময়ে স্বয়ংক্রিয়ভাবে প্রকাশ করা হয়, অর্থাৎ অ্যালগরিদম অবজেক্টের স্বয়ংক্রিয় ব্যবস্থাপনা ফাংশন রয়েছে। তাহলে অ্যালগরিদম অবজেক্ট স্বয়ংক্রিয়ভাবে প্রকাশিত হয় কখন? প্রথমত, যখন তার জীবনকাল শেষ হয়; দ্বিতীয়ত, যখন তার ফাংশনটিতে অস্বাভাবিকতা ঘটে। আপনি বলতে পারেন, এগুলি সবই স্বাভাবিক, বড় কিছু নয়। হ্যাঁ, বড় কিছু নয়। কিন্তু যতক্ষণ আমরা আরও গভীরভাবে যাব, ততক্ষণ সম্ভবত অপ্রত্যাশিত ফলন পাওয়া যাবে।
স্টাক অবজেক্ট, যখন স্বয়ংক্রিয়ভাবে মুক্তি পায়, তখন তার নিজস্ব বিশ্লেষণ ফাংশনকে ডেকে আনে। যদি আমরা স্টাকের অবজেক্টের মধ্যে সংস্থানগুলি আবদ্ধ করি এবং স্টাকের অবজেক্টের বিশ্লেষণ ফাংশনে সংস্থানগুলি মুক্ত করার কাজটি করি, তবে সংস্থানগুলি ফাঁস হওয়ার সম্ভাবনা হ্রাস পায়, কারণ স্টাক অবজেক্টগুলি স্বয়ংক্রিয়ভাবে সংস্থানগুলি মুক্ত করতে পারে, এমনকি যখন তাদের ফাংশনটি অস্বাভাবিক হয়। প্রকৃত প্রক্রিয়াটি হলঃ যখন ফাংশনটি অস্বাভাবিকতা ফেলে দেয়, তখন তথাকথিত stack_unwinding () ঘটে, অর্থাৎ স্ট্যাকটি প্রসারিত হয়, কারণ স্টাকের অবজেক্টগুলি প্রাকৃতিক স্টাকে বিদ্যমান থাকে, তাই স্টাকের রোলব্যাকের সময়, স্টাকের অবজেক্টের বিশ্লেষণ ফাংশনটি কার্যকর করা হয়, যার ফলে তাদের আবদ্ধ সংস্থানগুলি মুক্ত করা হয়। যদি না এই অস্বাভাবিকতা আবারও নির্বাহিত হয় তবে এটি খুব কম সম্ভাবনা রয়েছে, তাই স্টাকের অবজেক্টের সাথে সংস্থানগুলিকে আবদ্ধ করার সম্ভাবনা রয়েছে। আমরা এই
৪. স্ট্যাক অবজেক্ট তৈরি করা নিষিদ্ধ
উপরে উল্লেখ করা হয়েছে যে, আপনি যদি একটি নির্দিষ্ট ধরণের স্ট্যাক অবজেক্ট তৈরি করা থেকে বিরত থাকার সিদ্ধান্ত নেন, তাহলে আপনি নিজেই একটি রিসোর্স ক্যাপাসিটর ক্লাস তৈরি করতে পারেন, যা কেবলমাত্র স্ট্যাকের মধ্যে তৈরি করা যেতে পারে, যাতে অস্বাভাবিক পরিস্থিতিতে স্বয়ংক্রিয়ভাবে ক্যাপাসিটর থেকে রিসোর্স মুক্ত করা যায়।
তাহলে কিভাবে স্ট্যাক অবজেক্ট তৈরি করা নিষিদ্ধ করা যায়? আমরা জানি যে স্ট্যাক অবজেক্ট তৈরি করার একমাত্র উপায় হল new অপারেশন ব্যবহার করা। যদি আমরা new ব্যবহার করা নিষিদ্ধ করি তাহলে কি কাজ করবে না? আরও, new অপারেশনটি যখন চালানো হয় তখন new অপারেটরকে ডাকা হয়, এবং new অপারেটরটি পুনরায় লোড করা যায়। উপায় আছে, new অপারেটরকে ব্যক্তিগত করা, এবং সমান্তরাল হওয়ার জন্য, delete অপারেটরটিও পুনরায় লোড করা ভাল। এখন, আপনি আবার প্রশ্ন করতে পারেন, কি একটি টুকরো অবজেক্ট তৈরি করার জন্য new কল করার প্রয়োজন নেই? হ্যাঁ, প্রয়োজন নেই, কারণ একটি টুকরো অবজেক্ট তৈরি করার জন্য মেমরি অনুসন্ধান করার প্রয়োজন নেই, তবে সরাসরি স্ট্যাকের সূচকটি সামঞ্জস্য করুন এবং অবজেক্টটি চাপুন, এবং new অপারেটরের মূল কাজটি উপযুক্ত স্ট্যাক মেমরি অনুসন্ধান করা এবং স্ট্যাকের জন্য স্থান বরাদ্দকরণ করা, যা উপরে উল্লেখ করা হয়েছে। সুতরাং, আসুন আমরা নীচের উদাহরণ কোডটি দেখিঃ
#include <stdlib.h> //需要用到C式内存分配函数
class Resource ; //代表需要被封装的资源类
class NoHashObject
{
private:
Resource* ptr ;//指向被封装的资源
... ... //其它数据成员
void* operator new(size_t size) //非严格实现,仅作示意之用
{
return malloc(size) ;
}
void operator delete(void* pp) //非严格实现,仅作示意之用
{
free(pp) ;
}
public:
NoHashObject()
{
//此处可以获得需要封装的资源,并让ptr指针指向该资源
ptr = new Resource() ;
}
~NoHashObject()
{
delete ptr ; //释放封装的资源
}
};
NoHashObject এখন একটি ক্লাস যা স্ট্যাক অবজেক্ট নিষিদ্ধ করে, যদি আপনি নিম্নলিখিত কোডটি লিখেনঃ
NoHashObject* fp = new NoHashObject (() ; // কম্পাইলিং ত্রুটি!
delete fp ;
উপরের কোডটি কম্পাইলার ত্রুটি সৃষ্টি করবে। ঠিক আছে, এখন আপনি জানেন কিভাবে একটি ক্লাস ডিজাইন করা যায় যা স্ট্যাক অবজেক্টকে নিষিদ্ধ করে, আপনি হয়তো আমার মতই এই প্রশ্নটি করতে পারেন, যদি ক্লাস NoHashObject এর সংজ্ঞা পরিবর্তন না করা যায়, তাহলে কি এই ধরনের স্ট্যাক অবজেক্ট তৈরি করা সম্ভব নয়? না, এমন একটি পদ্ধতি আছে, আমি এটিকে বলব “হ্যাশ ফোর্স হ্যাকিং”। C++ এত শক্তিশালী যে আপনি এটির সাথে যা কিছু করতে চান তা করতে পারেন। এখানে মূলত কৌশলটি নির্দেশক প্রকারের জোরপূর্বক রূপান্তর।
void main(void)
{
char* temp = new char[sizeof(NoHashObject)] ;
//强制类型转换,现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp ;
temp = NULL ; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr ;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource() ;
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp ;//释放资源
temp = (char*)obj_ptr ;
obj_ptr = NULL ;//防止悬挂指针产生
delete [] temp ;//释放NoHashObject对象所占的堆空间。
}
উপরের বাস্তবায়নটি কষ্টকর, এবং বাস্তবায়নের এই পদ্ধতিটি বাস্তবে খুব কমই ব্যবহৃত হয়, তবে আমি এটি লিখেছি কারণ এটি বোঝা আমাদের সি ++ মেমরি অবজেক্টগুলি বোঝার জন্য উপকারী। উপরের এতগুলি বাধ্যতামূলক প্রকারের রূপান্তরগুলির মূল বিষয় কী? আমরা এটি বুঝতে পারিঃ
মেমরির একটি অংশে থাকা তথ্য অপরিবর্তিত থাকে, এবং প্রকারটি আমরা যে চশমাটি পরেছি তা। যখন আমরা একটি চশমা পরিধান করি, তখন আমরা মেমরিতে থাকা ডেটাকে সংশ্লিষ্ট প্রকারের সাথে ব্যাখ্যা করি, যাতে বিভিন্ন ব্যাখ্যা বিভিন্ন তথ্য দেয়।
এই ধরনের বাধ্যতামূলক রূপান্তর আসলে অন্য চশমা পরার পর একই ধরনের মেমরির তথ্য দেখার জন্য ব্যবহৃত হয়।
আরেকটি বিষয় মনে রাখতে হবে যে, বিভিন্ন কম্পাইলার অবজেক্টের সদস্য তথ্যের বিন্যাস ভিন্নভাবে করতে পারে, যেমন, বেশিরভাগ কম্পাইলার NoHashObject এর ptr পয়েন্টার সদস্যকে অবজেক্ট স্পেসের প্রথম ৪ বাইটে স্থাপন করে, যা নিশ্চিত করে যে নিচের এই বাক্যের রূপান্তরটি আমরা যেমনটি আশা করি তেমনভাবে সম্পাদন করা হবেঃ
Resource* rp = (Resource*)obj_ptr ;
তবে, সব কম্পাইলার এমন নয়।
যেহেতু আমরা একটি নির্দিষ্ট ধরণের স্ট্যাক অবজেক্ট তৈরি করতে নিষেধ করতে পারি, তাহলে কি আমরা এমন একটি ক্লাস ডিজাইন করতে পারি যাতে এটি স্ট্যাক অবজেক্ট তৈরি করতে না পারে? অবশ্যই পারি।
৫. কুমির তৈরি করা নিষিদ্ধ
পূর্বে উল্লেখ করা হয়েছে যে, যখন একটি কুলুঙ্গি অবজেক্ট তৈরি করা হয়, তখন কুলুঙ্গি নির্দেশকটি কুলুঙ্গি থেকে যথাযথ আকারের স্থানটি সরিয়ে নেওয়ার জন্য কুলুঙ্গি নির্দেশকটি সরানো হয়, তারপরে এই স্থানে একটি কুলুঙ্গি অবজেক্ট গঠনের জন্য সরাসরি সংশ্লিষ্ট কনস্ট্রাকশন ফাংশনকে ডাকা হয়, এবং যখন ফাংশনটি ফিরে আসে, তখন তার বিশ্লেষণ ফাংশনকে এই অবজেক্টটি মুক্ত করার জন্য ডাকা হয়, তারপরে কুলুঙ্গি নির্দেশকটি সামঞ্জস্য করে কুলুঙ্গি মেমোরিটি পুনরুদ্ধার করুন। এই প্রক্রিয়াটি অপারেটর নতুন / মুছে ফেলার অপারেশন প্রয়োজন হয় না, তাই অপারেটর নতুন / মুছে ফেলার অপারেটরটি বেসরকারী হিসাবে সেট করা যায় না। অবশ্যই উপরের বিবরণ থেকে, আপনি সম্ভবত ভাবছেনঃ একটি কুলুঙ্গি ফাংশন বা একটি কুলুঙ্গি ফাংশন সেটিং ব্যক্তিগত করুন, যাতে সিস্টেমটি কুলুঙ্গি / বিশ্লেষণ ফাংশন কল করতে
এটা সম্ভব, এবং আমি এই পদ্ধতিটি ব্যবহার করতে চাই। কিন্তু এর আগে, একটি বিষয় বিবেচনা করা দরকার যে, যদি আমরা নির্মাণ ফাংশনটি ব্যক্তিগত হিসাবে সেট করি, তাহলে আমরা সরাসরি স্ট্যাক অবজেক্ট তৈরি করতে new ব্যবহার করতে পারি না, কারণ new অবজেক্টের জন্য স্থান বরাদ্দ করার পরে তার নির্মাণ ফাংশনটিও কল করবে। তাই, আমি কেবল বিশ্লেষণ ফাংশনটি ব্যক্তিগত হিসাবে সেট করার পরিকল্পনা করছি। বিশ্লেষণের পরে, বিশ্লেষণ ফাংশনটি ব্যক্তিগত হিসাবে সেট করার পাশাপাশি কি অন্য কোনও প্রভাব রয়েছে? হ্যাঁ, এটি উত্তরাধিকারকেও সীমাবদ্ধ করবে।
যদি একটি শ্রেণী একটি বেস ক্লাস হিসাবে কাজ করতে চায় না, তাহলে সাধারণত তার বিভাজক ফাংশনকে private হিসেবে ঘোষণা করা হয়।
ক্যাশ অবজেক্টকে সীমাবদ্ধ করার জন্য, কিন্তু উত্তরাধিকারকে সীমাবদ্ধ না করে, আমরা ডিসক্রিপশন ফাংশনকে protected হিসাবে ঘোষণা করতে পারি, যা উভয়ই ভাল। নিম্নলিখিত কোডটি দেখায়ঃ
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
তারপর, আপনি NoStackObject ক্লাস ব্যবহার করতে পারেনঃ
NoStackObject* hash_ptr = new NoStackObject() ;
… … // hash_ptr নির্দেশিত বস্তুর উপর কাজ করে
hash_ptr->destroy() ; ওহ, এটা কি অদ্ভুত মনে হচ্ছে যে আমরা একটি বস্তু তৈরি করতে new ব্যবহার করি, কিন্তু delete ব্যবহার করে তা মুছে ফেলার পরিবর্তে destroy ব্যবহার করি। স্পষ্টতই, ব্যবহারকারীরা এই অদ্ভুত ব্যবহারের অভ্যস্ত নয়। তাই, আমি কনস্ট্রাকশন ফাংশনটি private বা protected হিসাবে সেট করার সিদ্ধান্ত নিয়েছি। এটি আবার উপরে এড়ানোর চেষ্টা করা প্রশ্নের দিকে ফিরে যায়, যা new ব্যবহার না করে, তাহলে একটি বস্তু তৈরি করার উপায় কী? আমরা একটি পরোক্ষ উপায় ব্যবহার করে এটি করতে পারি, অর্থাৎ এই শ্রেণীটি একটি স্ট্যাটিক সদস্য ফাংশন সরবরাহ করে যা এই ধরণের স্ট্যাক অবজেক্ট তৈরির জন্য বিশেষভাবে ব্যবহৃত হয়।
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
এখন আপনি NoStackObject ক্লাস ব্যবহার করতে পারেনঃ
NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
… … // hash_ptr নির্দেশিত বস্তুর উপর কাজ করে
hash_ptr->destroy() ;
hash_ptr = NULL ; // হ্যাশ পয়েন্টার ব্যবহার করা থেকে বিরত রাখুন
এখন মনে হচ্ছে, অবজেক্ট জেনারেট করা এবং অবজেক্ট রিলিজ করার কাজগুলো একসাথে করা হয়েছে।
অনেক C বা C++ প্রোগ্রামার জাঙ্ক রিসাইক্লিংকে ঘৃণা করে এবং মনে করে যে জাঙ্ক রিসাইক্লিং অবশ্যই ডায়নামিক মেমরি পরিচালনা করার চেয়ে কম কার্যকর এবং এটি অবশ্যই প্রোগ্রামটিকে সেখানে থামিয়ে দেবে, এবং যদি আপনি মেমরি পরিচালনা নিয়ন্ত্রণ করেন তবে বরাদ্দ এবং মুক্তির সময়টি স্থিতিশীল এবং প্রোগ্রামটি থামবে না। অবশেষে, অনেক C/C++ প্রোগ্রামার দৃ firm়ভাবে বিশ্বাস করে যে জাঙ্ক রিসাইক্লিং প্রক্রিয়াটি C/C++ এ বাস্তবায়িত করা যায় না। এই ভুল ধারণাগুলি জাঙ্ক রিসাইক্লিংয়ের অ্যালগরিদম সম্পর্কে অজ্ঞতার কারণে উদ্ভাবিত হয়েছে।
প্রকৃতপক্ষে, আবর্জনা পুনরুদ্ধারের প্রক্রিয়াটি ধীর নয়, এমনকি গতিশীল মেমরি বন্টনের চেয়েও বেশি কার্যকর। যেহেতু আমরা কেবল বন্টন করতে পারি না, তাই যখন মেমরি বন্টন করা হয় ত�