දළ විශ්ලේෂණය
අපට පිටපත් හා හුවමාරු මෝඩකම අවශ්ය වන්නේ ඇයි?
(අ සම්පතක් කළමනාකරණය කරන කිසිදු පන්ති දවටනය ස්මාර්ට් පහිටුම් දක්වනය මෙන්) ක්රියාත්මක කිරීමට අවශ්ය ද බිග් ත්රී . පිටපත් සාදන්නා සහ විනාශ කරන්නාගේ අරමුණු හා ක්රියාත්මක කිරීම සරල වුවත්, පිටපත් පැවරීමේ ක්රියාකරු වඩාත් සූක්ෂම හා දුෂ්කර ය. එය කළ යුත්තේ කෙසේද? වළක්වා ගත යුතු අන්තරායන් මොනවාද?
මෙම පිටපත්-සහ-swap බස් වහරක් විසඳුම වන අතර, අලංකාර දේවල් දෙකක් සපුරා ගැනීම සඳහා පැවරුම ක්රියාකරු සහාය: මග කේතය අනුපිටපතක් , සහ ලබා ශක්තිමත් හැර සහතිකයක් .
එය ක්රියාත්මක වන්නේ කෙසේද?
සංකල්පමය වශයෙන් , දත්ත වල දේශීය පිටපතක් නිර්මාණය කිරීම සඳහා පිටපත්-ඉදිකිරීම්කරුගේ ක්රියාකාරීත්වය භාවිතා කිරීමෙන් එය ක්රියාත්මක වන අතර පසුව පිටපත් කරන ලද දත්ත swap
ශ්රිතයක් සමඟ ගෙන පැරණි දත්ත නව දත්ත සමඟ හුවමාරු කර ගනී . පසුව තාවකාලික පිටපත විනාශ වන අතර පැරණි දත්ත රැගෙන යයි. නව දත්තවල පිටපතක් අපට ඉතිරිව ඇත.
පිටපත්-හා-හුවමාරු මෝඩකම භාවිතා කිරීම සඳහා, අපට කරුණු තුනක් අවශ්ය වේ: වැඩ කරන පිටපත් සාදන්නෙකු, වැඩ කරන විනාශ කරන්නෙකු (දෙකම ඕනෑම එතීමක පදනම වේ, එබැවින් කෙසේ හෝ සම්පූර්ණ විය යුතුය), සහ swap
ශ්රිතයක්.
Swap ශ්රිතය යනු විසි නොකරන ශ්රිතයක් වන අතර එය පන්තියක වස්තු දෙකක් මාරු කරයි . std::swap
අපේම දෑ ලබා දීම වෙනුවට භාවිතා කිරීමට අප පෙළඹෙනු ඇත , නමුත් මෙය කළ නොහැකි වනු ඇත; std::swap
එය ක්රියාත්මක කිරීමේදී පිටපත්-ඉදිකිරීම්කරු සහ පිටපත්-පැවරුම් ක්රියාකරු භාවිතා කරයි, අපි අවසානයේ උත්සාහ කරන්නේ පැවරුම් ක්රියාකරු නිර්වචනය කිරීමටයි!
(එපමණක් නොව, නුසුදුසු ඇමතුම් swap
අපගේ අභිරුචි හුවමාරු ක්රියාකරු භාවිතා කරනු ඇත, අපගේ පන්තියේ අනවශ්ය ඉදිකිරීම් සහ විනාශයන් මඟ std::swap
හැරෙනු ඇත.)
ගැඹුරු පැහැදිලි කිරීමක්
ඉලක්කය
කොන්ක්රීට් නඩුවක් සලකා බලමු. අපට අවශ්ය වන්නේ වෙනත් ආකාරයකින් වැඩකට නැති පන්තියක ගතික අරාවකි. අපි වැඩකරන ඉදිකිරීම්කරුවෙකු, පිටපත් සාදන්නෙකු සහ විනාශ කරන්නෙකු සමඟ ආරම්භ කරමු:
#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
විකරණය කොට තිබේ වනු ඇත. (එනම්, ප්රමාණය වැරදියි සහ දත්ත නැති වී ඇත!) ශක්තිමත් ව්යතිරේක සහතිකයක් සඳහා, එය සමාන දෙයක් විය යුතුය:
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;
}
කේතය පුළුල් වී ඇත! එය අපව තුන්වන ගැටලුවට යොමු කරයි: කේත අනුපිටපත් කිරීම. අපගේ පැවරුම් ක්රියාකරු අප දැනටමත් වෙනත් තැනක ලියා ඇති සියලුම කේත effectively ලදායී ලෙස අනුපිටපත් කරයි, එය භයානක දෙයකි.
අපගේ නඩුවේදී, එහි හරය පේළි දෙකක් පමණි (වෙන් කිරීම සහ පිටපත), නමුත් වඩාත් සංකීර්ණ සම්පත් සමඟ මෙම කේත පිපිරීම තරමක් කරදරයක් විය හැකිය. අප කිසි විටෙකත් නැවත නැවත නොකිරීමට උත්සාහ කළ යුතුය.
(කෙනෙකුට සිතිය හැකිය: එක් සම්පතක් නිවැරදිව කළමනාකරණය කිරීම සඳහා මෙම කේතය අවශ්ය නම්, මගේ පන්තිය එකකට වඩා කළමනාකරණය කරන්නේ නම් කුමක් කළ යුතුද? මෙය වලංගු කාරණයක් ලෙස පෙනෙන්නට තිබුණද, ඇත්ත වශයෙන්ම එය සුළුපටු නොවන try
/ catch
වගන්ති අවශ්ය වුවද, මෙය නොවන පංතියක් එක් සම්පතක් පමණක් කළමනාකරණය කළ යුතු නිසා !
සාර්ථක විසඳුමක්
සඳහන් කළ පරිදි, පිටපත්-හා-හුවමාරු මෝඩය මෙම සියලු ගැටලු විසඳනු ඇත. නමුත් මේ මොහොතේ, අපට එකක් හැර අනෙක් සියලුම අවශ්යතා ඇත: swap
ශ්රිතයක්. තුනේ රීතිය අපගේ පිටපත්-ඉදිකිරීම්කරු, පැවරුම් ක්රියාකරු සහ විනාශ කරන්නාගේ පැවැත්මට සාර්ථකව ඇතුළත් වන අතර, එය සැබවින්ම "ලොකු තුන සහ අඩක්" ලෙස හැඳින්විය යුතුය: ඔබේ පන්තිය සම්පතක් කළමනාකරණය කරන ඕනෑම වේලාවක එය 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;
}
අපට වැදගත් ප්රශස්තිකරණ අවස්ථාවක් අහිමි වේ. එපමණක් නොව, C ++ 11 හි මෙම තේරීම ඉතා වැදගත් වන අතර එය පසුව සාකච්ඡා කෙරේ. (සාමාන්ය සටහනක, ඉතා ප්රයෝජනවත් මාර්ගෝපදේශයක් පහත පරිදි වේ: ඔබ යම් ශ්රිතයක යම් පිටපතක් සෑදීමට යන්නේ නම්, එය පරාමිති ලැයිස්තුවේ සම්පාදකයාට කිරීමට ඉඩ දෙන්න. ‡)
කෙසේ හෝ වේවා, අපගේ සම්පත් ලබා ගැනීමේ මෙම ක්රමය කේත අනුපිටපත් ඉවත් කිරීම සඳහා යතුරයි: පිටපත සෑදීම සඳහා අපට පිටපත්-ඉදිකිරීම්කරුගෙන් කේතය භාවිතා කළ හැකි අතර, එයින් කිසි විටෙකත් නැවත නැවත කිරීමට අවශ්ය නොවේ. දැන් පිටපත සාදන ලද බැවින්, අපි හුවමාරු වීමට සූදානම්.
සියලුම නව දත්ත දැනටමත් වෙන් කර ඇති, පිටපත් කර ඇති අතර භාවිතයට සුදානම් බව ශ්රිතයට ඇතුළු වූ පසු නිරීක්ෂණය කරන්න. නොමිලේ අපට ව්යතිරේක සහතිකයක් ලබා දෙන්නේ මෙයයි: පිටපත තැනීම අසාර්ථක වුවහොත් අපි ශ්රිතයට ඇතුළු නොවන්නෙමු. එබැවින් තත්වය වෙනස් කළ නොහැක *this
. (ප්රබල ව්යතිරේක සහතිකයක් සඳහා අප මීට පෙර අතින් කළ දේ, සම්පාදකයා දැන් අප වෙනුවෙන් කරන්නේ; කෙතරම් කාරුණිකද?)
මේ අවස්ථාවේදී අපි ගෙදරින් නිදහස් වෙමු swap
. අපි අපගේ වර්තමාන දත්ත පිටපත් කළ දත්ත සමඟ හුවමාරු කර ගනිමින්, අපගේ තත්වය ආරක්ෂිතව වෙනස් කර, පැරණි දත්ත තාවකාලික බවට පත් කරමු. ශ්රිතය නැවත පැමිණෙන විට පැරණි දත්ත මුදා හරිනු ලැබේ. (පරාමිතියේ විෂය පථය අවසන් වන විට සහ එහි විනාශ කරන්නා ලෙස හැඳින්වේ.)
මෝඩය කිසිදු කේතයක් පුනරාවර්තනය නොකරන හෙයින්, අපට ක්රියාකරු තුළ දෝෂ හඳුන්වා දිය නොහැක. මෙයින් අදහස් කරන්නේ ස්වයං-පැවරුම් පරීක්ෂණයක අවශ්යතාවයෙන් අප ඉවත් වන අතර තනි ඒකාකාරී ක්රියාවලියක් සඳහා ඉඩ ලබා දෙන බවයි operator=
. (මීට අමතරව, අපට ස්වයං-පැවරුම් නොවන කාර්ය සාධන ද penalty ුවමක් තවදුරටත් නොමැත.)
එය පිටපත් හා හුවමාරු මෝඩකමයි.
C ++ 11 ගැන කුමක් කිව හැකිද?
C ++ හි ඊළඟ අනුවාදය, C ++ 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);
}
// ...
};
මොකද මෙතන වෙන්නෙ? චලනය-ඉදිකිරීමේ පරමාර්ථය සිහිපත් කරන්න: පංතියේ වෙනත් අවස්ථාවකින් සම්පත් ලබා ගැනීම, එය පැවරිය හැකි හා විනාශකාරී යැයි සහතික කළ හැකි තත්වයකට පත් කිරීම.
එබැවින් අප කර ඇති දේ ඉතා සරල ය: පෙරනිමි ඉදිකිරීම්කරු (C ++ 11 අංගයක්) හරහා ආරම්භ කරන්න, ඉන්පසු මාරු වන්න other
; අපගේ පංතියේ පෙරනිමියෙන් සාදන ලද නිදසුනක් ආරක්ෂිතව පැවරීමට හා විනාශ කිරීමට හැකි බව අපි දනිමු, එබැවින් other
හුවමාරු කිරීමෙන් පසුවද එය කළ හැකි බව අපි දනිමු .
(සමහර සම්පාදකයින් විසින් ඉදිකිරීම්කරුවන්ගේ නියෝජිත කණ්ඩායමට සහාය නොදක්වන බව සලකන්න. මේ අවස්ථාවේ දී, අප විසින් අතින් පෙරනිමිය පන්තිය තැනීමට සිදුවේ. මෙය අවාසනාවන්ත නමුත් වාසනාවකට මෙන් සුළු කාර්යයකි.)
එය ක්රියාත්මක වන්නේ ඇයි?
අපගේ පන්තියට අප විසින් කළ යුතු එකම වෙනස එයයි, එබැවින් එය ක්රියාත්මක වන්නේ ඇයි? පරාමිතිය වටිනාකමක් මිස යොමු කිරීමක් නොවීමට අප ගත් වැදගත් තීරණය මතක තබා ගන්න:
dumb_array& operator=(dumb_array other); // (1)
දැන්, other
අගය සමඟ ආරම්භ කරන්නේ නම්, එය චලනය වනු ඇත . පරිපූර්ණ. C ++ 03 ඒ හා සමානව, තර්කය අනුව අගය ගෙන අපගේ පිටපත්-ඉදිකිරීම්කරුගේ ක්රියාකාරිත්වය නැවත භාවිතා කරමු, C ++ 11 ස්වයංක්රීයව චලනය-ඉදිකිරීම්කරු සුදුසු විට තෝරා ගනු ඇත. (ඇත්ත වශයෙන්ම, කලින් සම්බන්ධිත ලිපියේ සඳහන් කර ඇති පරිදි, අගය පිටපත් කිරීම / ගෙනයාම හුදෙක් මුළුමනින්ම ඉවත් කළ හැකිය.)
ඒ නිසා පිටපත් හා හුවමාරු මෝඩකම අවසන් වේ.
පාද සටහන්
* අප mArray
අහෝසි කරන්නේ ඇයි? මක්නිසාද යත්, ක්රියාකරුගේ තවත් කේතයක් විසි කරන්නේ නම්, විනාශ කරන්නා ලෙස dumb_array
හැඳින්විය හැක; එය අහෝසි නොකර එය සිදුවුවහොත්, අපි දැනටමත් මකා දමා ඇති මතකය මකා දැමීමට උත්සාහ කරමු! ශුන්යය මකා දැමීම කිසිදු ක්රියාවක් නොවන බැවින් අපි එය ශුන්ය ලෙස සැකසීමෙන් වැළකී සිටිමු.
std::swap
Type අප අපේ වර්ගයට විශේෂ ize විය යුතු බවත් , පංතියේ swap
නොමිලේ ශ්රිතයක් ලබා දිය යුතු බවත් වෙනත් ප්රකාශයන් ඇත swap
. නමුත් මේ සියල්ල අනවශ්යය: නිසි ලෙස භාවිතා කිරීම swap
සුදුසුකම් නොලත් ඇමතුමක් හරහා වන අතර අපගේ කාර්යය වනු ඇත ADL හරහා හමු විය . එක් කාර්යයක් කරනු ඇත.
Simple හේතුව සරලයි: ඔබ සතුව සම්පතක් ඇති විට, ඔබට අවශ්ය ඕනෑම තැනකට එය මාරු කර / හෝ ගෙන යා හැකිය (C ++ 11). පරාමිති ලැයිස්තුවේ පිටපත සෑදීමෙන්, ඔබ උපරිම ප්රශස්තිකරණය කරයි.
Move චලනය වන ඉදිකිරීම්කරු සාමාන්යයෙන් විය යුතුය noexcept
, එසේ නොමැතිනම් සමහර කේත (උදා: std::vector
ප්රමාණය වෙනස් කිරීමේ තර්කනය) යම් පියවරක් අර්ථවත් වන විටදී පවා පිටපත් සාදන්නා භාවිතා කරයි. ඇත්ත වශයෙන්ම, ඇතුළත කේතය ව්යතිරේකයන් විසි නොකරන්නේ නම් පමණක් එය සලකුණු කරන්න.