সি ++ এর সাথে অনুশীলনে, আরআইআই কী, স্মার্ট পয়েন্টারগুলি কী কী , কোনও প্রোগ্রামে এগুলি কীভাবে প্রয়োগ করা হয় এবং স্মার্ট পয়েন্টারগুলির সাহায্যে আরআইআইআই ব্যবহার করার সুবিধা কী কী?
সি ++ এর সাথে অনুশীলনে, আরআইআই কী, স্মার্ট পয়েন্টারগুলি কী কী , কোনও প্রোগ্রামে এগুলি কীভাবে প্রয়োগ করা হয় এবং স্মার্ট পয়েন্টারগুলির সাহায্যে আরআইআইআই ব্যবহার করার সুবিধা কী কী?
উত্তর:
RAII এর একটি সাধারণ (এবং সম্ভবত অতিরিক্ত ব্যবহৃত) উদাহরণ ফাইল ক্লাস। RAII ব্যতীত কোডটি এমন কিছু দেখতে পারে:
File file("/path/to/file");
// Do stuff with file
file.close();
অন্য কথায়, আমাদের অবশ্যই নিশ্চিত করতে হবে যে ফাইলটি শেষ হয়ে গেলে আমরা এটি বন্ধ করে দেব। এটির দুটি ঘাটতি রয়েছে - প্রথমত, যেখানেই আমরা ফাইল ব্যবহার করি না কেন, আমাদের ফাইল :: বন্ধ () বলতে হবে - যদি আমরা এটি করতে ভুলে যাই তবে আমরা ফাইলটি আমাদের প্রয়োজনের চেয়ে বেশি ধরে রেখেছি। দ্বিতীয় সমস্যাটি কি আমরা যদি ফাইলটি বন্ধ করার আগে একটি ব্যতিক্রম ছুঁড়ে ফেলা হয়?
জাভা শেষ সমস্যাটি ব্যবহার করে দ্বিতীয় সমস্যাটি সমাধান করে:
try {
File file = new File("/path/to/file");
// Do stuff with file
} finally {
file.close();
}
বা জাভা 7-এর পরে, একটি রিসোর্স-স্টেটমেন্ট-সহ বিবৃতি:
try (File file = new File("/path/to/file")) {
// Do stuff with file
}
সি ++ উভয়ই আরআইআইআই ব্যবহার করে সমস্যার সমাধান করে - তা হ'ল ফাইলের ডেস্ট্রাক্টরে ফাইলটি বন্ধ করে দেওয়া। যতক্ষণ না সঠিক সময়ে ফাইল অবজেক্টটি ধ্বংস হয়ে যায় (যা এটি যাইহোক হওয়া উচিত) ফাইলটি বন্ধ করা আমাদের জন্য যত্ন নেওয়া হয়। সুতরাং, আমাদের কোডটি এখন এমন কিছু দেখাচ্ছে:
File file("/path/to/file");
// Do stuff with file
// No need to close it - destructor will do that for us
এটি জাভাতে করা যাবে না কারণ বস্তুটি কখন ধ্বংস হয়ে যাবে তার কোনও গ্যারান্টি নেই, সুতরাং ফাইলের মতো কোনও উত্স যখন মুক্ত হবে তখন আমরা গ্যারান্টি দিতে পারি না।
স্মার্ট পয়েন্টারগুলিতে - অনেক সময়, আমরা কেবল স্ট্যাকের উপর বস্তু তৈরি করি। উদাহরণস্বরূপ (এবং অন্য উত্তর থেকে একটি উদাহরণ চুরি):
void foo() {
std::string str;
// Do cool things to or using str
}
এটি সূক্ষ্মভাবে কাজ করে - তবে আমরা কী আবার ফিরে আসতে চাই? আমরা এটি লিখতে পারে:
std::string foo() {
std::string str;
// Do cool things to or using str
return str;
}
তো, তাতে দোষ কী? ঠিক আছে, রিটার্ন টাইপটি স্ট্যান্ডিং :: স্ট্রিং - সুতরাং এর অর্থ আমরা মান দিয়ে ফিরছি। এর অর্থ হ'ল আমরা আরআরটি অনুলিপি করছি এবং প্রকৃতপক্ষে অনুলিপিটি ফিরিয়ে দিই। এটি ব্যয়বহুল হতে পারে এবং আমরা এটি অনুলিপি করার খরচ এড়াতে চাই। সুতরাং, আমরা রেফারেন্স বা পয়েন্টার দ্বারা ফিরে আসার ধারণা নিয়ে আসতে পারি।
std::string* foo() {
std::string str;
// Do cool things to or using str
return &str;
}
দুর্ভাগ্যক্রমে, এই কোডটি কাজ করে না। আমরা স্ট্রিং-এ একটি পয়েন্টার ফিরিয়ে দিচ্ছি - তবে স্ট্রকে স্ট্যাক তৈরি করা হয়েছিল, সুতরাং ফু () থেকে বের হয়ে গেলে আমরা মুছে ফেলা হবে। অন্য কথায়, কলার পয়েন্টারটি পেয়ে যাওয়ার পরে, এটি অকেজো (এবং এটি ব্যবহারের ফলে অপব্যবহারের চেয়ে আরও খারাপ যেহেতু এটি ব্যবহার করা সমস্ত ধরণের মজাদার ত্রুটির কারণ হতে পারে)
তো, এর সমাধান কী? আমরা নতুন ব্যবহার করে স্তূপের উপর স্ট্র তৈরি করতে পারি - এইভাবে, যখন foo () সম্পন্ন হবে, str বিনষ্ট হবে না।
std::string* foo() {
std::string* str = new std::string();
// Do cool things to or using str
return str;
}
অবশ্যই, এই সমাধানটিও নিখুঁত নয়। কারণটি হ'ল আমরা তৈরি করেছি, তবে আমরা কখনই এটি মুছি না। এটি খুব ছোট প্রোগ্রামে সমস্যা নাও হতে পারে তবে সাধারণভাবে আমরা নিশ্চিত করতে চাই যে আমরা এটি মুছে ফেলেছি। আমরা কেবল এটি বলতে পারি যে কলারটিকে অবশ্যই তার সাথে এটি শেষ করে দেবে delete খারাপ দিকটি হ'ল কলারকে মেমরি পরিচালনা করতে হয় যা অতিরিক্ত জটিলতা যুক্ত করে এবং এটি ভুল হয়ে যেতে পারে, যার ফলে মেমরি ফাঁস হয় object
এখানেই স্মার্ট পয়েন্টারগুলি আসে The নীচের উদাহরণটি শেয়ারড_পিটার ব্যবহার করে - আমি আপনাকে পরামর্শ দিচ্ছি যে আপনি আসলে কী ব্যবহার করতে চান তা জানতে বিভিন্ন ধরণের স্মার্ট পয়েন্টারগুলি দেখুন।
shared_ptr<std::string> foo() {
shared_ptr<std::string> str = new std::string();
// Do cool things to or using str
return str;
}
এখন, ভাগ করা_পিটিআর স্ট্রিংয়ের উল্লেখের সংখ্যা গণনা করবে। এই ক্ষেত্রে
shared_ptr<std::string> str = foo();
shared_ptr<std::string> str2 = str;
এখন একই স্ট্রিংয়ের দুটি উল্লেখ রয়েছে। একবার স্ট্রের কোনও অবশিষ্ট রেফারেন্স না থাকলে এটি মুছে ফেলা হবে। এর মতো, আপনার নিজের আর এটি মুছে ফেলার বিষয়ে আর চিন্তা করতে হবে না।
দ্রুত সম্পাদনা: কিছু মন্তব্য যেমন উল্লেখ করেছে, এই উদাহরণটি (কমপক্ষে!) দুটি কারণে উপযুক্ত নয় for প্রথমত, স্ট্রিংগুলির প্রয়োগের কারণে, একটি স্ট্রিং অনুলিপি করা সস্তা বলে মনে হয়। দ্বিতীয়ত, নামকৃত রিটার্ন মান অপ্টিমাইজেশন হিসাবে পরিচিত হিসাবে, মান দ্বারা ফেরত ব্যয়বহুল নাও হতে পারে যেহেতু সংকলক জিনিসগুলি গতি বাড়ানোর জন্য কিছু চালাকি করতে পারে।
সুতরাং, আসুন আমাদের ফাইল ক্লাস ব্যবহার করে একটি ভিন্ন উদাহরণ চেষ্টা করুন।
ধরা যাক আমরা লগ হিসাবে কোনও ফাইল ব্যবহার করতে চাই। এর অর্থ আমরা কেবলমাত্র অ্যাপেন্ড মোডে আমাদের ফাইলটি খুলতে চাই:
File file("/path/to/file", File::append);
// The exact semantics of this aren't really important,
// just that we've got a file to be used as a log
এখন, আসুন কয়েক ফাইলের জন্য আমাদের ফাইলটিকে লগ হিসাবে সেট করুন:
void setLog(const Foo & foo, const Bar & bar) {
File file("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
দুর্ভাগ্যক্রমে, এই উদাহরণটি ভয়াবহভাবে শেষ হয় - এই পদ্ধতিটি শেষ হওয়ার সাথে সাথে ফাইলটি বন্ধ হয়ে যাবে, যার অর্থ foo এবং বারটির এখন একটি অবৈধ লগ ফাইল রয়েছে। আমরা স্তূপে ফাইল তৈরি করতে পারি এবং foo এবং বার উভয়কেই ফাইলের জন্য একটি পয়েন্টারটি দিতে পারি:
void setLog(const Foo & foo, const Bar & bar) {
File* file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
তবে তারপরে ফাইল মোছার দায় কে? যদি না হয় ফাইলটি মুছে ফেলা হয় তবে আমাদের মেমরি এবং রিসোর্স লিক উভয়ই রয়েছে। Foo বা বার ফাইলটি আগে শেষ করবে কিনা তা আমরা জানি না, তাই আমরা নিজে ফাইলটি মুছে ফেলার আশা করতে পারি না। উদাহরণস্বরূপ, foo ফাইলটি বারটি শেষ করার আগে মুছে ফেললে বারটির এখন একটি অবৈধ পয়েন্টার রয়েছে।
সুতরাং, আপনি যেমন অনুমান করতে পারেন, আমরা আমাদের সাহায্য করতে স্মার্ট পয়েন্টার ব্যবহার করতে পারি।
void setLog(const Foo & foo, const Bar & bar) {
shared_ptr<File> file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
এখন, কারও ফাইল মুছে ফেলার বিষয়ে চিন্তা করার দরকার নেই - একবার ফু ও বার উভয়ই শেষ হয়ে গেলে এবং ফাইলটির আর কোনও উল্লেখ নেই (সম্ভবত ফু ও বার নষ্ট হওয়ার কারণে) ফাইলটি স্বয়ংক্রিয়ভাবে মুছে ফেলা হবে।
RAII এটি একটি সাধারণ তবে দুর্দান্ত ধারণাটির জন্য একটি অদ্ভুত নাম। আরও ভাল নাম স্কোপ বাউন্ড রিসোর্স ম্যানেজমেন্ট (এসবিআরএম)। ধারণাটি হ'ল প্রায়শই আপনি কোনও ব্লকের শুরুতে সংস্থানগুলি বরাদ্দ করতে ঘটে থাকেন এবং এটি একটি ব্লকের প্রস্থানের সময় ছেড়ে দিতে হবে। ব্লক থেকে প্রস্থান করা স্বাভাবিক প্রবাহ নিয়ন্ত্রণের দ্বারা ঘটতে পারে, এ থেকে ঝাঁপিয়ে পড়ে এমনকি ব্যতিক্রম হয়ে। এই সমস্ত ক্ষেত্রে কভার করার জন্য, কোডটি আরও জটিল এবং অপ্রয়োজনীয় হয়ে যায়।
এসবিআরএম ছাড়াই এটি করার একটি উদাহরণ:
void o_really() {
resource * r = allocate_resource();
try {
// something, which could throw. ...
} catch(...) {
deallocate_resource(r);
throw;
}
if(...) { return; } // oops, forgot to deallocate
deallocate_resource(r);
}
আপনি দেখতে পাচ্ছেন যে অনেকগুলি উপায় রয়েছে আমরা বন্ধ হয়ে যেতে পারি। ধারণাটি হ'ল আমরা রিসোর্স ম্যানেজমেন্টকে একটি শ্রেণিতে আবদ্ধ করি। এর অবজেক্টের সূচনাটি সংস্থানটি অর্জন করে ("রিসোর্স অ্যাকুইজিশন ইজ ইনিশিয়ালাইজেশন")। যখন আমরা ব্লকটি (ব্লক স্কোপ) থেকে প্রস্থান করি, সেই সময় পুনরায় সংস্থানটি মুক্ত হয়।
struct resource_holder {
resource_holder() {
r = allocate_resource();
}
~resource_holder() {
deallocate_resource(r);
}
resource * r;
};
void o_really() {
resource_holder r;
// something, which could throw. ...
if(...) { return; }
}
এটি যদি আপনি তাদের নিজস্ব ক্লাসগুলি পেয়ে থাকেন যা কেবলমাত্র সম্পদ বরাদ্দ / অবনতির উদ্দেশ্যে নয়। বরাদ্দ তাদের কাজটি করার জন্য কেবল একটি অতিরিক্ত উদ্বেগ হবে। তবে যত তাড়াতাড়ি আপনি কেবল সংস্থানগুলি / ডিএলোকট রিসোর্স বরাদ্দ করতে চান, উপরেরটি অমান্য হয়ে যায়। আপনার প্রাপ্ত প্রতিটি ধরণের সংস্থানগুলির জন্য আপনাকে একটি মোড়ক দেওয়ার ক্লাস লিখতে হবে। এটি সহজ করার জন্য, স্মার্ট পয়েন্টারগুলি আপনাকে সেই প্রক্রিয়াটি স্বয়ংক্রিয় করতে দেয়:
shared_ptr<Entry> create_entry(Parameters p) {
shared_ptr<Entry> e(Entry::createEntry(p), &Entry::freeEntry);
return e;
}
সাধারণত, স্মার্ট পয়েন্টারগুলি নতুন / মুছার চারপাশে পাতলা মোড়ক থাকে যেগুলি delete
যখন তাদের নিজস্ব সম্পদ সুযোগের বাইরে চলে যায় তখন কেবল কল করতে হয় । কিছু স্মার্ট পয়েন্টার, যেমন শেয়ারড_পিটার আপনাকে তাদের একটি তথাকথিত মুছে ফেলার কথা বলতে দেয়, যা পরিবর্তে ব্যবহৃত হয় delete
। এটি আপনাকে, উদাহরণস্বরূপ, উইন্ডো হ্যান্ডলগুলি, নিয়মিত অভিব্যক্তি সংস্থান এবং অন্যান্য স্বেচ্ছাসেবী স্টাফ পরিচালনা করার অনুমতি দেয়, যতক্ষণ আপনি ডান মুছক সম্পর্কে শেয়ারড_পটারকে বলবেন।
বিভিন্ন উদ্দেশ্যে বিভিন্ন স্মার্ট পয়েন্টার রয়েছে:
কোড:
unique_ptr<plot_src> p(new plot_src); // now, p owns
unique_ptr<plot_src> u(move(p)); // now, u owns, p owns nothing.
unique_ptr<plot_src> v(u); // error, trying to copy u
vector<unique_ptr<plot_src>> pv;
pv.emplace_back(new plot_src);
pv.emplace_back(new plot_src);
অটো_পিটারের বিপরীতে, অনন্য_পিটার একটি পাত্রে রাখা যেতে পারে, কারণ ধারকগুলি অন-অনুলিপি (তবে চলমান) প্রকারগুলি যেমন স্ট্রিম এবং অনন্য_প্টরকে ধরে রাখতে সক্ষম হবে।
কোড:
void do_something() {
scoped_ptr<pipe> sp(new pipe);
// do something here...
} // when going out of scope, sp will delete the pointer automatically.
কোড:
shared_ptr<plot_src> p(new plot_src(&fx));
plot1->add(p)->setColor("#00FF00");
plot2->add(p)->setColor("#FF0000");
// if p now goes out of scope, the src won't be freed, as both plot1 and
// plot2 both still have references.
আপনি দেখতে পাচ্ছেন যে প্লট-সোর্স (ফাংশন এফএক্স) ভাগ করা হয়েছে, তবে প্রত্যেকের আলাদা আলাদা প্রবেশ রয়েছে, যার ভিত্তিতে আমরা রঙটি সেট করি। এখানে একটি দুর্বল_পিটার শ্রেণি রয়েছে যা কোডটি যখন স্মার্ট পয়েন্টারের মালিকানাধীন সংস্থানটি উল্লেখ করতে হয় তখন ব্যবহৃত হয়, তবে সংস্থানটির মালিকানার প্রয়োজন হয় না। কোনও কাঁচা পয়েন্টার পাস করার পরিবর্তে আপনার তখন একটি দুর্বল_পিটার তৈরি করা উচিত। এটি কোনও ব্যতিক্রম ছুঁড়ে ফেলা হবে যখন আপনি দুর্বল_পিটার অ্যাক্সেস পাথ দ্বারা সংস্থানটি অ্যাক্সেস করার চেষ্টা করবেন তা লক্ষ্য করে, যদিও সেখানে এখন আর কোনও সংস্থান_পিতরের সংস্থান নেই।
unique_ptr
এবং sort
একইভাবে পরিবর্তন করাও যায়।
RAII হ'ল নকশা দৃষ্টান্ত এটি নিশ্চিত করার জন্য যে ভেরিয়েবলগুলি তাদের নির্মাতাদের সমস্ত প্রয়োজনীয় আরম্ভ এবং তাদের ডেস্ট্রাক্টরগুলিতে সমস্ত প্রয়োজনীয় ক্লিনআপ পরিচালনা করে। এটি এক এক ধাপে সমস্ত সূচনা এবং পরিষ্কারকরণ হ্রাস করে।
সি ++ এর জন্য আরআইআইআইয়ের প্রয়োজন হয় না, তবে এটি ক্রমবর্ধমানভাবে গ্রহণযোগ্য যে RAII পদ্ধতিগুলি ব্যবহার করার ফলে আরও শক্তিশালী কোড তৈরি হবে।
আরআইআই সি ++ এ দরকারী যে কারণটি হ'ল সি ++ ভেরিয়েবলগুলি প্রবেশের সময় এবং সুযোগটি প্রবেশের সাথে সাথে অভ্যন্তরীণভাবে তৈরি এবং ধ্বংস পরিচালনা করে, কোনও ব্যতিক্রমের দ্বারা সাধারণ কোড প্রবাহের মাধ্যমে বা স্টাইন্ড আনওয়াইন্ডিং দিয়ে শুরু করে। এটি সি ++ এ একটি ফ্রিবি।
এই প্রক্রিয়াগুলিতে সমস্ত সূচনা এবং ক্লিনআপ বেঁধে, আপনি নিশ্চিত হন যে সি ++ আপনার জন্যও এই কাজের যত্ন নেবে।
সি ++ এ আরআইআইআই সম্পর্কে কথা বলা সাধারণত স্মার্ট পয়েন্টারগুলির আলোচনার দিকে পরিচালিত করে, কারণ ক্লিনআপের ক্ষেত্রে পয়েন্টারগুলি বিশেষত ভঙ্গুর হয়। ম্যালোক বা নতুন থেকে অর্জিত হিপ-বরাদ্দ মেমরি পরিচালনা করার সময়, পয়েন্টারটি বিনষ্ট হওয়ার আগে প্রোগ্রামারটির সাধারণত সেই স্মৃতি মুক্ত বা মুছে ফেলা দায়। স্মার্ট পয়েন্টারগুলি RAII দর্শন ব্যবহার করবে তা নিশ্চিত করতে যে পয়েন্টার ভেরিয়েবলটি যে কোনও সময় নষ্ট হওয়া বরাদ্দ বস্তুগুলি ধ্বংস হয়ে যায়।
স্মার্ট পয়েন্টারটি RAII এর একটি প্রকরণ। আরআইআই মানে রিসোর্স অধিগ্রহণ হ'ল সূচনা। স্মার্ট পয়েন্টার ব্যবহারের আগে একটি সংস্থান (মেমরি) অর্জন করে এবং তারপরে এটি কোনও ডিস্ট্রাক্টরে স্বয়ংক্রিয়ভাবে ফেলে দেয়। দুটি জিনিস ঘটে:
উদাহরণস্বরূপ, আরেকটি উদাহরণ হ'ল নেটওয়ার্ক সকেট আরআইআইআই। এক্ষেত্রে:
এখন, আপনি দেখতে পাচ্ছেন, বেশিরভাগ ক্ষেত্রে RAII একটি খুব দরকারী সরঞ্জাম কারণ এটি মানুষকে পাথর কাটাতে সহায়তা করে।
সি ++ স্মার্ট পয়েন্টারগুলির উত্স আমার উপরে প্রতিক্রিয়া সহ নেটটির আশেপাশে কয়েক মিলিয়নে রয়েছে।
বুস্টের শেয়ার্ড মেমরির জন্য অন্তর্ভুক্ত রয়েছে including এটি মেমরির ব্যবস্থাপনাকে ব্যাপকভাবে সরল করে তোলে, বিশেষত মাথা ব্যথা-প্ররোচিত পরিস্থিতিতে যেমন আপনার একই ডেটা স্ট্রাকচার ভাগ করে নেওয়ার সময় 5 টি প্রক্রিয়া থাকে: যখন সবাই মেমরির একটি অংশ নিয়ে সম্পন্ন হয়, আপনি চান যে এটি স্বয়ংক্রিয়ভাবে মুক্তি পাবে এবং এটি বের করার চেষ্টা করে সেখানে বসে থাকতে হবে না delete
মেমরির এক অংশকে কল করার জন্য কে দায়বদ্ধ হতে পারে , তা না হলে আপনি মেমরি ফাঁস, অথবা এমন একটি পয়েন্টার যা ভুল করে দু'বার মুক্তি পেয়ে পুরো মস্তকে নষ্ট করতে পারে।
অকার্যকর foo () { std :: স্ট্রিং বার; // // আরও কোড এখানে // }
যাই ঘটুক না কেন, বারে foo () ফাংশনটির সুযোগটি রেখে দেওয়া হলে বারটি সঠিকভাবে মুছে ফেলা হবে।
অভ্যন্তরীণ std :: স্ট্রিং বাস্তবায়ন প্রায়শই রেফারেন্স গণনা পয়েন্টার ব্যবহার করে। সুতরাং কেবলমাত্র অভ্যন্তরীণ স্ট্রিংটি অনুলিপি করা দরকার যখন তারগুলির একটি অনুলিপি পরিবর্তন হয়। অতএব স্মার্ট পয়েন্টার হিসাবে গণ্য হওয়া একটি রেফারেন্স কেবলমাত্র যখন প্রয়োজন তখন কিছু অনুলিপি করা সম্ভব করে তোলে।
তদতিরিক্ত, অভ্যন্তরীণ রেফারেন্স গণনা এটি সম্ভব করে তোলে যে অভ্যন্তরীণ স্ট্রিংয়ের অনুলিপিটির আর প্রয়োজন নেই হলে মেমরিটি সঠিকভাবে মুছে ফেলা হবে।