সংক্ষিপ্ত বিবরণ
কেন আমাদের অনুলিপি-অনুলিপি প্রয়োজন?
যে কোনও শ্রেণি একটি সংস্থান পরিচালনা করে (একটি স্মরণ পয়েন্টারের মতো একটি মোড়ক ) বিগ থ্রি বাস্তবায়ন করতে হবে । অনুলিপি-নির্মাণকারী এবং ধ্বংসকারীদের লক্ষ্য এবং বাস্তবায়ন সোজা হলেও কপি-এসাইনমেন্ট অপারেটর যুক্তিযুক্তভাবে সবচেয়ে সংক্ষিপ্ত এবং কঠিন difficult এটি কিভাবে করা উচিত? কী সমস্যাগুলি এড়ানো প্রয়োজন?
কপি-এবং-swap 'র বাগ্ধারা সমাধান, এবং এইরূপ সূচারূভাবে দুটি জিনিস অর্জনের নিয়োগ অপারেটর সহায়তা: এড়ানো কোড অনুলিপি , এবং একটি প্রদানের শক্তিশালী ব্যতিক্রম গ্যারান্টি ।
এটা কিভাবে কাজ করে?
ধারণাগতভাবে , এটি ডেটার স্থানীয় অনুলিপি তৈরি করতে অনুলিপি-নির্মাণকারীর কার্যকারিতা ব্যবহার করে কাজ করে, তারপরে অনুলিপি করা swap
ডেটাটিকে কোনও ফাংশন সহ নিয়ে যায়, নতুন ডেটা দিয়ে পুরানো ডেটা অদলবদল করে। তারপরে অস্থায়ী অনুলিপিটি পুরানো ডেটা সাথে রাখে taking আমরা নতুন তথ্য একটি অনুলিপি বাকি আছে।
কপি এবং অদলবদ প্রজ্ঞাটি ব্যবহার করার জন্য, আমাদের তিনটি জিনিস দরকার: একটি ওয়ার্কিং কপি-কনস্ট্রাক্টর, একটি ওয়ার্কিং ডেস্ট্রাক্টর (উভয়ই কোনও মোড়কের ভিত্তি, সুতরাং যাইহোক সম্পূর্ণ হওয়া উচিত) এবং একটি swap
ফাংশন।
একটি অদলবদল ফাংশন একটি নন-নিক্ষেপ ফাংশন যা কোনও শ্রেণীর দুটি বস্তু, সদস্য হিসাবে সদস্যের অদলবদল করে। আমরা std::swap
আমাদের নিজস্ব সরবরাহ না করে ব্যবহার করার জন্য প্রলুব্ধ হতে পারি , তবে এটি অসম্ভব হবে; std::swap
কপি-কনস্ট্রাক্টর এবং কপি-এসাইনমেন্ট অপারেটরটিকে তার প্রয়োগের মধ্যে ব্যবহার করে এবং আমরা শেষ পর্যন্ত নিজের দিক থেকে অ্যাসাইনমেন্ট অপারেটরটি সংজ্ঞায়িত করার চেষ্টা করব!
(কেবল swap
এটিই নয়, অযোগ্য কলগুলি আমাদের কাস্টম সোয়াপ অপারেটরটি ব্যবহার করবে, আমাদের শ্রেণীর অপ্রয়োজনীয় নির্মাণ এবং ধ্বংসের বিষয়টি বাদ std::swap
দেবে would)
একটি গভীর গভীরতা
লক্ষ
আসুন একটি কংক্রিট কেস বিবেচনা করা যাক। আমরা অন্যথায় অকেজো শ্রেণিতে, একটি গতিশীল অ্যারে পরিচালনা করতে চাই। আমরা একটি ওয়ার্কিং কনস্ট্রাক্টর, কপি-কনস্ট্রাক্টর এবং ডেস্ট্রাক্টর দিয়ে শুরু করি:
#include <algorithm> // std::copy
#include <cstddef> // std::size_t
class dumb_array
{
public:
// (default) constructor
dumb_array(std::size_t size = 0)
: mSize(size),
mArray(mSize ? new int[mSize]() : nullptr)
{
}
// copy-constructor
dumb_array(const dumb_array& other)
: mSize(other.mSize),
mArray(mSize ? new int[mSize] : nullptr),
{
// note that this is non-throwing, because of the data
// types being used; more attention to detail with regards
// to exceptions must be given in a more general case, however
std::copy(other.mArray, other.mArray + mSize, mArray);
}
// destructor
~dumb_array()
{
delete [] mArray;
}
private:
std::size_t mSize;
int* mArray;
};
এই শ্রেণিটি প্রায় সাফল্যের সাথে অ্যারে পরিচালনা করে তবে এটি operator=
সঠিকভাবে কাজ করা দরকার।
একটি ব্যর্থ সমাধান
একটি নিষ্পাপ বাস্তবায়ন দেখতে কেমন হতে পারে তা এখানে:
// the hard part
dumb_array& operator=(const dumb_array& other)
{
if (this != &other) // (1)
{
// get rid of the old data...
delete [] mArray; // (2)
mArray = nullptr; // (2) *(see footnote for rationale)
// ...and put in the new
mSize = other.mSize; // (3)
mArray = mSize ? new int[mSize] : nullptr; // (3)
std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
}
return *this;
}
এবং আমরা বলি আমরা শেষ করেছি; এটি এখন ফাঁস ছাড়াই একটি অ্যারে পরিচালনা করে। যাইহোক, এটি তিনটি সমস্যায় ভুগছে, কোডটিতে ধারাবাহিকভাবে চিহ্নিত (n)
।
প্রথমটি স্ব-নিয়োগ পরীক্ষা। এই চেকটি দুটি উদ্দেশ্যে কাজ করে: এটি স্ব-নিয়োগের ক্ষেত্রে অহেতুক কোড চালানো থেকে রক্ষা করার সহজ উপায় এবং এটি আমাদের সূক্ষ্ম বাগগুলি থেকে রক্ষা করে (যেমন কেবল চেষ্টা করার জন্য এবং অনুলিপি করার জন্য অ্যারেটি মুছে ফেলা)। তবে অন্যান্য সমস্ত ক্ষেত্রে এটি কেবল প্রোগ্রামটি ধীর করে দেওয়ার এবং কোডটিতে শোরগোল হিসাবে কাজ করার জন্য কাজ করে; স্ব-অ্যাসাইনমেন্ট খুব কমই ঘটে থাকে, তাই বেশিরভাগ সময় এই চেকটি অপচয় হয়। অপারেটরটি ছাড়া এগুলি সঠিকভাবে কাজ করতে পারলে ভাল হয়।
দ্বিতীয়টি হ'ল এটি কেবল একটি প্রাথমিক ব্যতিক্রম গ্যারান্টি সরবরাহ করে। যদি new int[mSize]
ব্যর্থ হয়, *this
ঈষত্ পরিবর্তিত হয়েছে হবে। (যথা, আকারটি ভুল এবং ডেটা চলে গেছে!) দৃ exception় ব্যতিক্রম গ্যারান্টির জন্য এটির অনুরূপ কিছু হওয়া দরকার:
dumb_array& operator=(const dumb_array& other)
{
if (this != &other) // (1)
{
// get the new data ready before we replace the old
std::size_t newSize = other.mSize;
int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
// replace the old data (all are non-throwing)
delete [] mArray;
mSize = newSize;
mArray = newArray;
}
return *this;
}
কোডটি প্রসারিত হয়েছে! যা আমাদের তৃতীয় সমস্যার দিকে নিয়ে যায়: কোড নকল। আমাদের অ্যাসাইনমেন্ট অপারেটর কার্যকরভাবে আমরা ইতিমধ্যে অন্য কোথাও লিখেছি সমস্ত কোড নকল করে, এবং এটি একটি ভয়ানক জিনিস।
আমাদের ক্ষেত্রে, এর মূলটি কেবল দুটি লাইন (বরাদ্দ এবং অনুলিপি), তবে আরও জটিল সংস্থান সহ এই কোডটি ব্লাট বেশ ঝামেলা হতে পারে। আমাদের নিজেদেরকে কখনই পুনরাবৃত্তি করার চেষ্টা করা উচিত।
(যে কেউ ভাবতে পারেন: যদি কোনও সংস্থান সঠিকভাবে পরিচালনা করার জন্য যদি এই অনেক কোডের প্রয়োজন হয় তবে আমার শ্রেণি যদি একের অধিক পরিচালনা করে তবে কী এটি একটি বৈধ উদ্বেগ বলে মনে হতে পারে, এবং প্রকৃতপক্ষে এটি অ-তুচ্ছ try
/ catch
দফা প্রয়োজন, এটি একটি অ -সিস। এটি কারণ যে কোনও শ্রেণীর কেবল একটি সংস্থান পরিচালনা করা উচিত !)
একটি সফল সমাধান
উল্লিখিত হিসাবে, অনুলিপি-অনুলিপি আইডিয়ম এই সমস্ত সমস্যার সমাধান করবে। তবে এই মুহুর্তে আমাদের একগুলি ছাড়া সমস্ত প্রয়োজনীয়তা রয়েছে: একটি swap
ফাংশন। যদিও তিনটি বিধিটি সফলভাবে আমাদের অনুলিপি-নির্মাতা, অ্যাসাইনমেন্ট অপারেটর এবং ডেস্ট্রাক্টরের অস্তিত্বকে আবশ্যক করে, সত্যই এটি "দ্য বিগ থ্রি এবং এ হাফ" বলা উচিত: যে কোনও সময় আপনার শ্রেণি কোনও সংস্থান পরিচালনা করে এটি কোনও swap
ফাংশন সরবরাহ করার জন্য অর্থবোধ করে ।
আমাদের ক্লাসে অদলবদল কার্যকারিতা যুক্ত করতে হবে, এবং আমরা এটি নিম্নলিখিতভাবে করি †:
class dumb_array
{
public:
// ...
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
// enable ADL (not necessary in our case, but good practice)
using std::swap;
// by swapping the members of two objects,
// the two objects are effectively swapped
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// ...
};
( কেন তার ব্যাখ্যা এখানেpublic friend swap
;) এখন আমরা কেবল আমাদের dumb_array
গুলি বদল করতে পারি না , তবে সাধারণভাবে অদলবদল আরও কার্যকর হতে পারে; এটি সম্পূর্ণ অ্যারে বরাদ্দ এবং অনুলিপি করার পরিবর্তে কেবলমাত্র পয়েন্টার এবং আকারকে অদলবদল করে। কার্যকারিতা এবং দক্ষতার এই বোনাস বাদে, আমরা এখন অনুলিপি-অনুলিপি প্রতিস্থাপন করতে প্রস্তুত।
আরও অ্যাডো ছাড়া আমাদের অ্যাসাইনমেন্ট অপারেটরটি হ'ল:
dumb_array& operator=(dumb_array other) // (1)
{
swap(*this, other); // (2)
return *this;
}
এবং এটাই! একটি হতাশ হয়ে পড়ে, তিনটিই সমস্যা একবারে সুন্দরভাবে মোকাবেলা করা হয়েছে।
কেন এটি কাজ করে?
আমরা প্রথমে একটি গুরুত্বপূর্ণ পছন্দ লক্ষ্য করি: প্যারামিটারের যুক্তিটি মান অনুসারে নেওয়া হয় । যদিও কেউ কেবল সহজেই নিম্নলিখিতগুলি করতে পারে (এবং সত্যই, প্রচলিত আইডিয়মের অনেক নিষ্পাপ বাস্তবায়ন):
dumb_array& operator=(const dumb_array& other)
{
dumb_array temp(other);
swap(*this, temp);
return *this;
}
আমরা একটি গুরুত্বপূর্ণ অপ্টিমাইজেশনের সুযোগ হারাচ্ছি । কেবল এটিই নয়, সি ++ 11-এ এই পছন্দটি সমালোচিত, যা পরে আলোচনা করা হয়েছে। (একটি সাধারণ নোটে, উল্লেখযোগ্যভাবে দরকারী গাইডলাইনটি নিম্নরূপ: আপনি যদি কোনও ফাংশনে কোনও কিছুর অনুলিপি তৈরি করতে যাচ্ছেন তবে সংকলকটি প্যারামিটার তালিকায় এটি করতে দিন ‡)
যেভাবেই হোক না কেন, আমাদের সংস্থান অর্জনের এই পদ্ধতিটি কোড সদৃশতা দূর করার মূল চাবিকাঠি: আমরা অনুলিপিটি তৈরি করতে কপি-কনস্ট্রাক্টরের কাছ থেকে কোডটি ব্যবহার করতে পারি, এবং এর কোনও বিট পুনরায় করার দরকার নেই। এখন অনুলিপিটি তৈরি হয়ে গেছে, আমরা অদলবদলের জন্য প্রস্তুত।
ফাংশনে প্রবেশ করার পরে লক্ষ্য করুন যে সমস্ত নতুন ডেটা ইতিমধ্যে বরাদ্দ, অনুলিপি এবং ব্যবহারের জন্য প্রস্তুত। এটি আমাদের নিখরচায় একটি শক্তিশালী ব্যতিক্রমের গ্যারান্টি দেয়: অনুলিপিটি নির্মাণে ব্যর্থ হলে আমরা এমনকি ফাংশনে প্রবেশ করব না এবং তাই এর অবস্থার পরিবর্তন করা সম্ভব নয় *this
। (দৃ exception় ব্যতিক্রমী গ্যারান্টির জন্য আমরা ম্যানুয়ালি এর আগে যা করেছি, সংকলকটি এখন আমাদের জন্য করছে; কত দয়ালু))
এই মুহুর্তে আমরা হোম-মুক্ত, কারণ swap
নিক্ষিপ্ত নয়। আমরা আমাদের বর্তমান তথ্য অনুলিপি করে অনুলিপি করা তথ্য দিয়ে নিরাপদে আমাদের রাষ্ট্র পরিবর্তন করে এবং পুরাতন তথ্য অস্থায়ী মধ্যে রাখা হয়। ফাংশন ফিরে আসার পরে পুরানো ডেটা প্রকাশ করা হয়। (যেখানে প্যারামিটারের স্কোপ শেষ হবে এবং এর ধ্বংসকারীকে ডাকা হবে))
যেহেতু প্রতিমাটি কোনও কোড পুনরাবৃত্তি করে না, আমরা অপারেটরের মধ্যে বাগগুলি প্রবর্তন করতে পারি না। নোট করুন যে এর অর্থ আমরা একটি স্ব-কার্য-নির্ধারণের চেকের প্রয়োজনীয়তা থেকে মুক্তি পেয়েছি, এর একক ইউনিফর্ম বাস্তবায়নের অনুমতি দিচ্ছি operator=
। (অতিরিক্তভাবে, অ-স্ব-নিয়োগের ক্ষেত্রে আমাদের আর পারফরম্যান্স পেনাল্টি নেই))
এবং এটি হ'ল প্রতিলিপি এবং অনুলিপি।
সি ++ 11 সম্পর্কে কী হবে?
সি ++, সি ++ 11 এর পরবর্তী সংস্করণটি কীভাবে আমরা সংস্থানগুলি পরিচালনা করি তার মধ্যে একটি অত্যন্ত গুরুত্বপূর্ণ পরিবর্তন ঘটায়: তিনটির বিধি এখন চারজনের নিয়ম (এবং দেড়)। কেন? যেহেতু কেবল আমাদের সংস্থান অনুলিপি করতে সক্ষম হওয়া প্রয়োজন তা নয় , আমাদের পাশাপাশি এটি স্থানান্তর-নির্মাণও প্রয়োজন ।
ভাগ্যক্রমে আমাদের জন্য, এটি সহজ:
class dumb_array
{
public:
// ...
// move constructor
dumb_array(dumb_array&& other) noexcept ††
: dumb_array() // initialize via default constructor, C++11 only
{
swap(*this, other);
}
// ...
};
এখানে কি হচ্ছে? সরানো-নির্মাণের লক্ষ্যটি স্মরণ করুন: শ্রেণীর অন্য একটি উদাহরণ থেকে সংস্থান গ্রহণের জন্য, এটিকে কার্যনির্বাহযোগ্য এবং ধ্বংসাত্মক হতে পারে এমন গ্যারান্টিযুক্ত রাজ্যে রেখে।
সুতরাং আমরা যা করেছি তা সহজ: ডিফল্ট কনস্ট্রাক্টর (একটি সি ++ 11 বৈশিষ্ট্য) এর মাধ্যমে সূচনা করুন, তারপরে অদলবদল করুন other
; আমরা জানি আমাদের ক্লাসের একটি ডিফল্ট নির্মিত উদাহরণ নিরাপদে নির্ধারিত ও ধ্বংস হতে পারে, তাই আমরা জানি other
যে অদলবদল করার পরেও এটি করতে সক্ষম হব।
(মনে রাখবেন যে কয়েকটি সংকলক নির্মাণকারী প্রতিনিধিদের সমর্থন করে না; এই ক্ষেত্রে আমাদের ক্লাসটি ম্যানুয়ালি ডিফল্ট করতে হবে This এটি দুর্ভাগ্যজনক তবে ভাগ্যক্রমে তুচ্ছ কাজ)
কেন যে কাজ করে?
আমাদের ক্লাসে এটিই করা আমাদের একমাত্র পরিবর্তন, তবে এটি কেন কাজ করে? পরামিতিটিকে একটি মান হিসাবে উল্লেখ করার জন্য আমরা যে-গুরুত্বপূর্ণ সিদ্ধান্ত নিয়েছি তা মনে রাখুন, রেফারেন্স নয়:
dumb_array& operator=(dumb_array other); // (1)
এখন, যদি other
কোনও মূল্যায়ন দিয়ে সূচনা করা হচ্ছে , তবে এটি সরানো-নির্মিত হবে । পারফেক্ট। একই ভাবে সি ++ সালে 03, সি ++ 11 হবে আমাদের দ্বারা মান যুক্তি গ্রহণ করে আমাদের কপি-কন্সট্রাকটর কার্যকারিতা পুনরায় ব্যবহার করার অনুমতি স্বয়ংক্রিয়ভাবে যখন পাশাপাশি উপযুক্ত পদক্ষেপ-কন্সট্রাকটর বাছাই। (এবং অবশ্যই, পূর্বের লিঙ্কযুক্ত নিবন্ধে উল্লিখিত হিসাবে, মানটির অনুলিপি / চলনটি কেবল পুরোপুরিভাবে আলাদা করা যেতে পারে))
এবং তাই অনুলিপি-অনুলিপি আইডিয়াম।
পাদটিকা
* আমরা কেন mArray
বাতিল করব? কারণ অপারেটরের যদি আরও কোনও কোড নিক্ষেপ করে তবে এর ডেস্ট্রাক্টর dumb_array
বলা যেতে পারে; এবং যদি এটি বাতিল না করেই ঘটে থাকে তবে আমরা ইতিমধ্যে মুছে ফেলা মেমরিটি মুছে ফেলার চেষ্টা করি! আমরা এটিকে নালায় সেট করে এড়িয়ে চলি, কেননা নালটি মুছে ফেলা কোনও অপারেশন।
Other অন্যান্য দাবী রয়েছে যে std::swap
আমাদের আমাদের ধরণের জন্য বিশেষীকরণ করা উচিত , একটি শ্রেণির swap
পাশাপাশি একটি ফ্রি-ফাংশন swap
ইত্যাদি সরবরাহ করা উচিত But তবে এগুলি সমস্ত অপ্রয়োজনীয়: কোনও সঠিক ব্যবহারের swap
অযোগ্য কলের মাধ্যমে হবে এবং আমাদের কাজটি হবে এডিএল মাধ্যমে পাওয়া । একটি ফাংশন করবে।
Reason কারণটি সহজ: আপনার নিজের কাছে একবার সংস্থান হয়ে গেলে আপনি অদলবদল করতে পারেন এবং / অথবা এটি (সি ++ 11) যে কোনও জায়গায় সরিয়ে নিতে পারেন। এবং প্যারামিটার তালিকায় অনুলিপি তৈরি করে, আপনি সর্বোত্তমকরণ করুন।
Move মুভ কনস্ট্রাক্টরটি সাধারণত হওয়া উচিত noexcept
, অন্যথায় কিছু কোড (যেমন: std::vector
পুনরায় আকার দেওয়ার লজিক) অনুলিপি তৈরির ক্ষেত্রেও কোনও পদক্ষেপটি বোধগম্য হবে। অবশ্যই, অভ্যন্তর কোডটি ব্যতিক্রম না ছড়িয়ে দিলে কেবল এটিকেই চিহ্নিত করুন।