c++ - উনল - সি++ বই




R+এবং সি++ এ স্মার্ট পয়েন্টার (4)

প্রমান এবং কারণ ধারণা, সহজ।

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

সি ++ রাইয়ের প্রয়োজন নেই, তবে এটি ক্রমবর্ধমানভাবে স্বীকার করে যে RAII পদ্ধতি ব্যবহার করে আরো শক্তসমর্থ কোড তৈরি করা হবে।

C ++ তে RAII ব্যবহারযোগ্য কারণ হল C ++ ভেরিয়েবলগুলির সৃষ্টি এবং ধ্বংসকে অন্তর্নিহিতভাবে পরিচালনা করে এবং সুযোগটি ছেড়ে দেয়, স্বাভাবিক কোড প্রবাহের মাধ্যমে বা স্ট্যাকের মাধ্যমে ব্যতিক্রমকে অকার্যকর করে। যে সি ++ একটি freebie হয়।

এই প্রক্রিয়াগুলিতে সমস্ত প্রারম্ভিকতা এবং পরিচ্ছন্নতা টাইপ করে, আপনি নিশ্চিত হন যে C ++ আপনার জন্য এই কাজটির যত্ন নেবে।

C ++ তে RAII সম্পর্কে কথা বলা সাধারণত স্মার্ট পয়েন্টারগুলির আলোচনার দিকে পরিচালিত করে, কারণ এটি পরিষ্কার করার সময় পয়েন্টারগুলি বিশেষত ভঙ্গুর। ম্যালোক বা নতুন থেকে প্রাপ্ত হিপ-বরাদ্দকৃত মেমরি পরিচালনা করার সময়, পয়েন্টারটি ধ্বংস হওয়ার আগে সাধারণত এটির মেমরি মুক্ত বা মুছতে প্রোগ্রামারের দায়িত্ব। পয়েন্টার ভেরিয়েবলটি যে কোনও সময় ধ্বংস হয়ে গেলে বরাদ্দকৃত বস্তুগুলি ধ্বংস করা হয় তা নিশ্চিত করতে স্মার্ট পয়েন্টাররা রায়ের দর্শনের ব্যবহার করবে।

সি ++ এর সাথে অনুশীলনে, RAII কী, স্মার্ট পয়েন্টারগুলি কী , কোন প্রোগ্রামে এগুলি প্রয়োগ করা হয় এবং স্মার্ট পয়েন্টারগুলির সাথে RAI ব্যবহারের সুবিধা কী?


একটি সহজ (এবং সম্ভবত overused) 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();
}

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

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
}

এটি জরিমানা কাজ করে - কিন্তু যদি আমরা str ফিরে আসতে চান? আমরা এই লিখতে পারে:

std::string foo() {
    std::string str;
    // Do cool things to or using str
    return str;
}

সুতরাং, যে কি ভুল? আচ্ছা, রিটার্ন টাইপ হল std :: স্ট্রিং - সুতরাং এর অর্থ হল আমরা মান দ্বারা ফিরে আসছি। এর মানে হল আমরা str অনুলিপি করি এবং প্রকৃতপক্ষে কপিটি ফেরত দিই। এটি ব্যয়বহুল হতে পারে এবং আমরা এটি অনুলিপি করার খরচ এড়াতে চাই। অতএব, আমরা রেফারেন্স দ্বারা বা পয়েন্টার দ্বারা ফিরে আসার ধারণা নিয়ে আসতে পারে।

std::string* foo() {
    std::string str;
    // Do cool things to or using str
    return &str;
}

দুর্ভাগ্যবশত, এই কোড কাজ করে না। আমরা str তে একটি পয়েন্টার ফিরে আসছি - কিন্তু str স্ট্যাকে তৈরি হয়েছিল, তাই আমরা foo () থেকে বের হয়ে গেলে মুছে ফেলা হবে। অন্য কথায়, কলার পয়েন্টার পায় যখন, এটি নিরর্থক (এবং এটা ব্যবহার করে নিরর্থক চেয়ে arguably খারাপ খারাপ সব ধরনের ক্ষিপ্ত ত্রুটি হতে পারে)

সুতরাং, সমাধান কি? আমরা নতুন ব্যবহার করে হিপ-এ স্ট্র তৈরি করতে পারি - যেভাবে, যখন foo () সম্পন্ন হয়, str তা ধ্বংস হবে না।

std::string* foo() {
    std::string* str = new std::string();
    // Do cool things to or using str
    return str;
}

অবশ্যই, এই সমাধান হয় নিখুঁত নয়। কারণ আমরা str সৃষ্টি করেছি, কিন্তু আমরা এটি মুছে ফেলব না। এটি একটি খুব ছোট প্রোগ্রামে সমস্যা হতে পারে না তবে সাধারণভাবে, আমরা এটি মুছে ফেলতে চাই কিনা তা নিশ্চিত করতে চাই। আমরা কেবলমাত্র বলতে পারি যে কলকাতার সাথে এটি সমাপ্ত হওয়ার পরেই বস্তু মুছে ফেলতে হবে। নেতিবাচক দিক হল যে কলারকে মেমরি পরিচালনা করতে হবে, যা অতিরিক্ত জটিলতা যোগ করে এবং এটি ভুল হতে পারে, এটি একটি মেমরি লিক অর্থাৎ অদৃশ্য হওয়া সত্ত্বেও বস্তুটি মুছে ফেলবে না।

এখানে স্মার্ট পয়েন্টার আসে। নিচের উদাহরণটি shared_ptr ব্যবহার করে - আমি আপনাকে আসলে কী ব্যবহার করতে চান তা শিখতে বিভিন্ন ধরণের স্মার্ট পয়েন্টারগুলি দেখানোর পরামর্শ দিচ্ছি।

shared_ptr<std::string> foo() {
    shared_ptr<std::string> str = new std::string();
    // Do cool things to or using str
    return str;
}

এখন, share_ptr str তে রেফারেন্সের সংখ্যা গণনা করবে। এই ক্ষেত্রে

shared_ptr<std::string> str = foo();
shared_ptr<std::string> str2 = str;

এখন একই স্ট্রিং দুটি রেফারেন্স আছে। Str তে কোন অবশিষ্ট রেফারেন্স নেই, এটি মুছে ফেলা হবে। এভাবে, আপনাকে এটি নিজেকে মুছে ফেলার বিষয়ে আর চিন্তা করতে হবে না।

দ্রুত সম্পাদনা: মন্তব্যের কিছু উল্লেখ করা হয়েছে, এই উদাহরণটি (অন্তত!) দুটি কারণে নিখুঁত নয়। প্রথমত, স্ট্রিং বাস্তবায়নের কারণে, একটি স্ট্রিং অনুলিপি সস্তা হতে থাকে। দ্বিতীয়ত, নামযুক্ত রিটার্ন মান অপ্টিমাইজেশান হিসাবে পরিচিত হিসাবে, মান দ্বারা ফেরত ব্যয়বহুল হতে পারে না কারণ কম্পাইলার কিছু গতিশীলতা করতে পারে গতি বাড়ানোর জন্য।

সুতরাং, আমাদের ফাইল ক্লাস ব্যবহার করে একটি ভিন্ন উদাহরণ চেষ্টা করুন।

চলুন আমরা একটি লগ হিসাবে একটি ফাইল ব্যবহার করতে চান। এর অর্থ আমরা কেবলমাত্র মোড যুক্ত করতে আমাদের ফাইলটি খুলতে চাই:

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 এবং bar এ একটি অবৈধ লগ ফাইল রয়েছে। আমরা হিপের ফাইলটি তৈরি করতে পারি এবং foo এবং bar উভয়তে ফাইল করতে একটি পয়েন্টার পাস করতে পারি:

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);
}

এখন, কাউকে ফাইল মুছে ফেলার বিষয়ে চিন্তা করতে হবে না - একবার foo এবং bar উভয় শেষ হয়ে গেলে এবং ফাইলের কোনও রেফারেন্স নেই (সম্ভবত foo এবং বারটি ধ্বংস হচ্ছে), ফাইলটি স্বয়ংক্রিয়ভাবে মুছে ফেলা হবে।


Boost.Interprocess শেয়ারকৃত স্মৃতির জন্য Boost.Interprocess এগুলির মধ্যে একটি সংখ্যা রয়েছে। এটি মেমরি পরিচালনাকে ব্যাপকভাবে সরল করে তোলে, বিশেষত মাথাব্যাথা-প্রবণতা পরিস্থিতিগুলিতে যখন আপনার 5 টি প্রসেস একই তথ্য কাঠামো ভাগ করে: যখন প্রত্যেকে মেমরির একটি অংশের সাথে সম্পন্ন হয়, তখন আপনি এটি স্বয়ংক্রিয়ভাবে মুক্ত পেতে চান এবং খুঁজে বের করার চেষ্টা করতে বসতে হয় না মেমরির একটি অংশে delete জন্য কে দায়ী হতে পারে, যাতে আপনি মেমরি লিকের সাথে শেষ না হন, অথবা একটি পয়েন্টার যা ভুলভাবে দ্বিগুণভাবে মুক্ত হয়ে যায় এবং সম্পূর্ণ হিপটিকে দূষিত করে।


RAII এটি একটি সহজ কিন্তু অসাধারণ ধারণাটির জন্য একটি অদ্ভুত নাম। নাম স্কোপ বাইন্ড রিসোর্স ম্যানেজমেন্ট (SBRM) ভাল। ধারণাটি হল যে আপনি প্রায়ই ব্লকের শুরুতে সংস্থান বরাদ্দ করতে থাকেন এবং ব্লকের প্রস্থানে এটি মুক্ত করতে হবে। ব্লকটি প্রস্থান স্বাভাবিক প্রবাহ নিয়ন্ত্রণ দ্বারা, এটির বাইরে ঝাঁপিয়ে এবং এমনকি ব্যতিক্রম দ্বারাও ঘটতে পারে। এই সমস্ত ক্ষেত্রে কভার করার জন্য, কোডটি আরও জটিল এবং অকার্যকর হয়ে ওঠে।

শুধু এসবিআরএম ছাড়াই এটি একটি উদাহরণ:

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);
}

আপনি দেখতে পাই আমরা অনেকগুলি উপায় পাই। ধারণা আমরা একটি বর্গ মধ্যে সম্পদ ব্যবস্থাপনা encapsulate হয়। তার বস্তুর সূচনাটি সম্পদ অর্জন করে ("রিসোর্স অ্যাকুইজিশন ইনিশিয়ালাইজেশন")। আমরা ব্লক (ব্লক সুযোগ) থেকে প্রস্থান করার সময়, সম্পদ আবার মুক্তি হয়।

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 হয়ে যায় যখন তাদের নিজস্ব সংস্থানটি সুযোগের বাইরে চলে যায়। কিছু স্মার্ট পয়েন্টার, যেমন share_ptr আপনাকে তাদের তথাকথিত ডিলিটার বলতে দেয়, যা delete পরিবর্তে ব্যবহার করা হয়। এটি আপনাকে উইন্ডো হ্যান্ডলগুলি, রেগুলার এক্সপ্রেশন রিসোর্স এবং অন্যান্য ইচ্ছাকৃত স্টাফ পরিচালনা করার অনুমতি দেয়, যতক্ষণ আপনি ডান মুছে ফেলার বিষয়ে shared_ptr বলবেন।

বিভিন্ন উদ্দেশ্যে বিভিন্ন স্মার্ট পয়েন্টার আছে:

unique_ptr

একটি স্মার্ট পয়েন্টার যা একচেটিয়াভাবে একটি বস্তুর মালিক। এটি বুস্ট নয়, তবে এটি সম্ভবত পরবর্তী সি ++ স্ট্যান্ডার্ডে উপস্থিত হবে। এটি নন-অনুলিপিযোগ্য কিন্তু হস্তান্তর-মালিকানা সমর্থন করে । কিছু উদাহরণ কোড (পরবর্তী সি ++):

কোড:

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);

Auto_ptr এর বিপরীতে, unique_ptrটি একটি ধারক হিসাবে রাখা যেতে পারে, কারণ কন্টেইনারগুলি অননুমোদিত (কিন্তু চলমান) ধরনের স্ট্রিম এবং অনন্য_পৃষ্ঠের মতো রাখতে সক্ষম হবে।

scoped_ptr

একটি বুস্ট স্মার্ট পয়েন্টার যা কপি বা অস্থাবর নয়। যখন আপনি স্কোপ থেকে বের হয়ে গেলে নিশ্চিত হবেন যে পয়েন্টারগুলি মুছে ফেলা হয় তখন এটি ব্যবহার করার জন্য উপযুক্ত জিনিস।

কোড:

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

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

কোড:

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. 

আপনি দেখতে হিসাবে, প্লট-উৎস (ফাংশন FX) ভাগ করা হয়, কিন্তু প্রতিটি একটি পৃথক এন্ট্রি আছে, যা আমরা রঙ সেট। একটি weak_ptr ক্লাস রয়েছে যা কোডটি যখন স্মার্ট পয়েন্টারের মালিকানাধীন সংস্থার কাছে উল্লেখ করার প্রয়োজন হয়, তবে সংস্থার মালিক হওয়ার প্রয়োজন হয় না। একটি কাঁচা পয়েন্টার পাস করার পরিবর্তে, আপনি একটি দুর্বল_পৃষ্ঠ তৈরি করা উচিত। যখন এটি একটি দুর্বল_পৃক্ত অ্যাক্সেস পথের দ্বারা সম্পদটি অ্যাক্সেস করার চেষ্টা করে তখন এটি একটি ব্যতিক্রম নিক্ষেপ করবে, যদিও কোনও সংস্থান মালিকানাধীন কোনো ভাগ_পৃষ্ঠা নেই।





raii