اصطلاح کپی و جابجایی چیست و چه زمانی باید از آن استفاده شود؟چه مشکلی را حل می کند؟آیا برای C ++ 11 تغییر می کند؟
ایده خوب برای داشتن توضیحی کامل برای این اصطلاح ، آنقدر معمول است که همه باید در مورد آن بدانند.
هشدار: اصطلاحات کپی/مبادله به مراتب بیشتر از آنچه مفید است استفاده می شود. در صورت عدم نیاز به ضمانت ایمنی استثناء قوی از انتساب کپی ، اغلب برای عملکرد مضر است. و هنگامی که ایمنی استثناء قوی برای تکالیف کپی مورد نیاز است ، علاوه بر یک اپراتور تکلیف کپی بسیار سریعتر ، به راحتی توسط یک عملکرد عمومی کوتاه نیز ارائه می شود. به SlidEshare. net/ripplelabs/howard-hiant-accu2014 Slides 43-53 مراجعه کنید. خلاصه: کپی/مبادله ابزاری مفید در جعبه ابزار است. اما بیش از حد بازاریابی شده است و متعاقباً مورد سوء استفاده قرار گرفته است.
howardhiant: بله ، 1 به آن. من این را در زمانی نوشتم که تقریباً در هر سؤال C ++ "کمک به کلاس من هنگام کپی کردن به آن کمک کرد" و این پاسخ من بود. این مناسب است که شما فقط می خواهید کار کپی/حرکت/حرکت یا هر چیز دیگری را انجام دهید تا بتوانید به سمت چیزهای دیگر بروید ، اما واقعاً بهینه نیست. اگر فکر می کنید این کمک می کند ، یک سلب مسئولیت را در بالای پاسخ من قرار دهید.
5 پاسخ 5
بررسی اجمالی
چرا ما به اصطلاح کپی و جابجایی نیاز داریم؟
هر کلاس که یک منبع را مدیریت کند (یک بسته بندی ، مانند یک نشانگر هوشمند) برای اجرای سه بزرگ نیاز دارد. در حالی که اهداف و اجرای کپی سازنده و ویرانگر ساده است ، اما اپراتور طراحی کپی مسلماً ظریف ترین و دشوارترین است. چگونه باید انجام شود؟از چه خطایی باید جلوگیری شود؟
اصطلاح کپی و جابجایی راه حل است و با ظرافت به اپراتور واگذاری در دستیابی به دو چیز کمک می کند: جلوگیری از تکثیر کد و ارائه ضمانت استثنائی قوی.
چگونه کار می کند؟
از نظر مفهومی ، این کار با استفاده از عملکرد کپی سازنده برای ایجاد یک کپی محلی از داده ها کار می کند ، سپس داده های کپی شده را با یک عملکرد مبادله می گیرد و داده های قدیمی را با داده های جدید مبادله می کند. نسخه موقت سپس تخریب می شود و داده های قدیمی را با آن می گیرد. ما با یک کپی از داده های جدید باقی مانده ایم.
برای استفاده از اصطلاح کپی و جابجایی ، به سه چیز نیاز داریم: یک کپی سازنده کار ، یک ویرانگر کار (هر دو اساس هر بسته بندی هستند ، بنابراین به هر حال باید کامل باشد) و یک عملکرد مبادله.
یک تابع مبادله یک تابع غیر پرتاب است که دو شیء یک کلاس ، عضو را برای عضو تعویض می کند. ما ممکن است وسوسه شویم که به جای تهیه خودمان ، از STD :: مبادله استفاده کنیم ، اما این غیرممکن خواهد بود. STD :: SWAP در اجرای خود از اپراتور کپی سازنده و کپی کردن کپی استفاده می کند ، و ما در نهایت سعی خواهیم کرد که اپراتور واگذاری را از نظر خود تعریف کنیم!
.
توضیحی عمیق
هدف
بیایید یک مورد مشخص را در نظر بگیریم. ما می خواهیم ، در یک کلاس در غیر این صورت بی فایده ، یک آرایه پویا را مدیریت کنیم. ما با یک سازنده کار ، کپی سازنده و ویرانگر شروع می کنیم:
این کلاس تقریباً آرایه را با موفقیت مدیریت می کند ، اما برای کار صحیح به اپراتور = نیاز دارد.
یک راه حل شکست خورده
در اینجا چگونه یک اجرای ساده لوحانه به نظر می رسد:
و ما می گوییم که تمام شده ایم. این اکنون یک آرایه را بدون نشت مدیریت می کند. با این حال ، از سه مشکل رنج می برد ، که به طور متوالی در کد به عنوان (n) مشخص شده است.
اولین آزمون خودکشی است. این بررسی دو هدف را انجام می دهد: این یک روش آسان برای جلوگیری از اجرای کد بی نیاز در مورد خودکشی است و ما را از اشکالات ظریف محافظت می کند (مانند حذف آرایه فقط برای امتحان کردن و کپی کردن آن). اما در همه موارد دیگر صرفاً برای کاهش سرعت برنامه و به عنوان سر و صدای کد عمل می کند. خودآگاهی به ندرت رخ می دهد ، بنابراین بیشتر اوقات این چک یک زباله است. بهتر است که اپراتور بتواند بدون آن به درستی کار کند.
دوم این است که فقط یک ضمانت استثناء اساسی را ارائه می دهد. اگر Int [msize] جدید ناکام باشد ، *این اصلاح شده است.(یعنی ، اندازه اشتباه است و داده ها از بین رفته است!) برای یک ضمانت استثناء قوی ، باید چیزی شبیه به آن باشد:
کد گسترش یافته است! که ما را به سمت مشکل سوم سوق می دهد: کپی برداری کد.
اپراتور واگذاری ما به طور مؤثر تمام کدی را که قبلاً در جای دیگر نوشتیم کپی می کند ، و این یک چیز وحشتناک است.
در مورد ما ، هسته اصلی آن فقط دو خط (تخصیص و کپی) است ، اما با منابع پیچیده تری این کد نفخ می تواند کاملاً دردسر باشد. ما باید تلاش کنیم تا هرگز خودمان را تکرار کنیم.
(ممکن است کسی تعجب کند: اگر این کد بسیار لازم برای مدیریت صحیح یک منبع باشد ، اگر کلاس من بیش از یک را مدیریت کند ، چه می شود؟ در حالی که به نظر می رسد این یک نگرانی معتبر است ، و در واقع به بندهای آزمایشی / گرفتن غیر واقعی نیاز دارد ، این استیک مسئله غیرقانونی است به این دلیل است که یک کلاس باید فقط یک منبع را مدیریت کند!)
یک راه حل موفق
همانطور که گفته شد ، اصطلاح کپی و جابجایی همه این موارد را برطرف می کند. اما در حال حاضر ، ما تمام الزامات را به جز یک مورد داریم: یک عملکرد مبادله. در حالی که قاعده سه با موفقیت مستلزم وجود کپی سازنده ، اپراتور تکالیف و ویرانگر ما است ، واقعاً باید آن را "سه و نیم بزرگ" خوانده شود: هر زمان که کلاس شما یک منبع را مدیریت کند ، همچنین منطقی است که مبادله ای را ارائه دهدتابع.
ما باید عملکرد مبادله را به کلاس خود اضافه کنیم و این کار را به شرح زیر انجام می دهیم †:
(در اینجا توضیح می دهد که چرا دوست عمومی مبادله می شود.) اکنون نه تنها می توانیم Dumb_array خود را عوض کنیم ، بلکه به طور کلی مبادله می تواند کارآمدتر باشد. این فقط به جای اختصاص و کپی کردن کل آرایه ها ، نشانگرها و اندازه ها را مبادله می کند. گذشته از این جایزه در عملکرد و کارآیی ، اکنون ما آماده اجرای اصطلاح کپی و جابجایی هستیم.
بدون آزار و اذیت بیشتر ، اپراتور تکلیف ما:
و همین است! با یک ضربه محکم و ناگهانی ، هر سه مشکل به یکباره برطرف می شوند.
چرا کار می کند؟
ما ابتدا به یک انتخاب مهم توجه می کنیم: آرگومان پارامتر براساس ارزش گرفته می شود. در حالی که می توان موارد زیر را به راحتی انجام داد (و در واقع ، بسیاری از پیاده سازی های ساده لوحانه از اصطلاحات):
ما یک فرصت بهینه سازی مهم را از دست می دهیم. نه تنها این ، بلکه این انتخاب در C ++ 11 بسیار مهم است ، که بعداً مورد بحث قرار می گیرد.(در یک یادداشت کلی ، یک راهنمای بسیار مفید به شرح زیر است: اگر می خواهید یک کپی از چیزی را در یک تابع تهیه کنید ، بگذارید کامپایلر این کار را در لیست پارامتر انجام دهد.
در هر صورت ، این روش برای به دست آوردن منبع ما کلید از بین بردن تکثیر کد است: ما می توانیم از کپی سازنده کپی برای تهیه نسخه استفاده کنیم و هرگز نیازی به تکرار هر بیت از آن نیست. اکنون که نسخه ساخته شده است ، ما آماده مبادله هستیم.
مشاهده کنید که پس از ورود به عملکرد که تمام داده های جدید قبلاً اختصاص داده شده ، کپی شده و آماده استفاده هستند. این همان چیزی است که به ما یک ضمانت استثناء قوی را به صورت رایگان به ما می دهد: ما حتی در صورت عدم موفقیت ساخت کپی ، وارد عملکرد نمی شویم و بنابراین امکان تغییر وضعیت *این امکان پذیر نیست.(کاری که قبلاً برای ضمانت استثناء قوی انجام دادیم ، کامپایلر اکنون برای ما انجام می دهد ؛ چقدر مهربان است.)
در این مرحله ما بدون خانه هستیم ، زیرا مبادله غیر پرتاب است. ما داده های فعلی خود را با داده های کپی شده تعویض می کنیم ، با خیال راحت حالت خود را تغییر می دهیم و داده های قدیمی به موقت وارد می شوند. داده های قدیمی با بازگشت عملکرد منتشر می شوند.(جایی که بر روی دامنه پارامتر به پایان می رسد و ویرانگر آن خوانده می شود.)
از آنجا که اصطلاح هیچ کد را تکرار نمی کند ، ما نمی توانیم اشکالات موجود در اپراتور را معرفی کنیم. توجه داشته باشید که این بدان معنی است که ما از نیاز به یک بررسی خودکشی خلاص شده ایم ، و این امکان را می دهد که یکنواخت یکنواخت اپراتور =.(علاوه بر این ، ما دیگر مجازات عملکردی در مورد عدم تعهدات خود نداریم.)
و این اصطلاح کپی و جابجایی است.
در مورد C ++ 11 چطور؟
نسخه بعدی C ++ ، C ++ 11 ، یک تغییر بسیار مهم را در نحوه مدیریت منابع ایجاد می کند: قاعده سه اکنون قاعده چهار (و نیم) است. چرا؟از آنجا که نه تنها ما باید بتوانیم منابع خود را کپی کنیم ، بلکه باید آن را نیز ساخت.
خوشبختانه برای ما ، این آسان است:
اینجا چه خبره؟هدف از ساخت و ساز حرکت: برای گرفتن منابع از نمونه دیگری از کلاس ، ترک آن در حالت تضمین شده قابل تعیین و تخریب.
بنابراین آنچه ما انجام داده ایم ساده است: از طریق سازنده پیش فرض (یک ویژگی C ++ 11) اولیه کنید ، سپس با دیگری مبادله کنید. ما می دانیم که یک نمونه پیش فرض ساخته شده از کلاس ما با خیال راحت می تواند اختصاص و ویران شود ، بنابراین می دانیم که دیگران پس از تعویض قادر به انجام همین کار هستند.
(توجه داشته باشید که برخی از کامپایلرها از نمایندگان سازنده پشتیبانی نمی کنند ؛ در این حالت ، ما باید به صورت دستی کلاس را بسازیم. این یک کار ناگوار اما خوشبختانه بی اهمیت است.)
چرا این کار می کند؟
این تنها تغییری است که ما باید به کلاس خود بسازیم ، پس چرا کار می کند؟تصمیم همیشه مهم را که ما گرفتیم برای ایجاد پارامتر به یک مقدار و نه یک مرجع بخاطر بسپارید:
حال اگر دیگران با یک RValue آغاز شود ، ساخت آن ساخته می شود. کامل. به همین ترتیب C ++ 03 اجازه دهید با استفاده از آرگومان براساس ارزش ، از عملکرد کپی-سازنده خود استفاده کنیم ، C ++ 11 در صورت لزوم به طور خودکار حرکت سازنده را انتخاب می کند.(و البته ، همانطور که در مقاله قبلی مرتبط ذکر شده است ، کپی/جابجایی ارزش ممکن است به سادگی به طور کلی انتخاب شود.)
و بنابراین اصطلاح کپی و تعویض را نتیجه می گیرد.
پانویسها و منابع
*چرا ما Marray را تهی می کنیم؟زیرا اگر کد دیگری در اپراتور پرتاب شود ، ممکن است تخریب کننده dumb_array نامیده شود. و اگر این اتفاق بدون آن رخ دهد که آن را تهی کند ، ما سعی می کنیم حافظه ای را که قبلاً حذف شده است حذف کنیم! ما با تنظیم آن در تهی ، از این کار اجتناب می کنیم ، زیرا حذف NULL یک عمل نیست.
† ادعاهای دیگری وجود دارد که ما باید STD :: مبادله ای را برای نوع خود تخصص دهیم ، یک مبادله درون کلاس را در کنار یک تعویض با عملکرد آزاد و غیره فراهم کنیم. تماس بگیرید ، و عملکرد ما از طریق ADL یافت می شود. یک عملکرد انجام خواهد داد.
‡ دلیل این ساده است: هنگامی که منبع خود را برای خود دارید ، می توانید در هر کجا که لازم باشد ، آن را عوض کرده و/یا آن را جابجا کنید (C ++ 11). و با تهیه نسخه در لیست پارامتر ، بهینه سازی را به حداکثر می رسانید.
†† سازنده Move به طور کلی باید Noxcept باشد ، در غیر این صورت برخی از کد ها (به عنوان مثال STD :: منطق تغییر اندازه وکتور) از سازنده کپی استفاده می کنند حتی اگر یک حرکت معقول باشد. البته ، اگر کد داخل استثنائاتی نباشد ، فقط آن را علامت گذاری کنید.
GMAN: من استدلال می کنم که یک کلاس که چندین منبع را به طور همزمان مدیریت می کند محکوم به شکست است (ایمنی استثناء کابوس می شود) و من اکیداً توصیه می کنم که یا یک کلاس یک منبع را مدیریت کند یا دارای عملکرد و کارکرد تجاری باشد و از مدیران استفاده کند.
Neuviemeporte: با پرانتز ، عناصر آرایه به طور پیش فرض اولیه هستند. بدون ، آنها ناآگاه نیستند. از آنجا که در سازنده کپی به هر حال ما مقادیر را بازنویسی می کنیم ، می توانیم از ابتدای کار خودداری کنیم.
Neuviemeporte: اگر می خواهید در اکثر کد های عمومی که با آن روبرو خواهید شد ، مانند Boost :: مبادله و سایر موارد مختلف مبادله ای ، در طول ADL پیدا کنید. مبادله یک مسئله دشوار در C ++ است ، و به طور کلی همه ما موافقت کرده ایم که یک نقطه دسترسی واحد بهترین است (برای قوام) و تنها راه انجام این کار به طور کلی یک عملکرد رایگان است (Int نمی تواند یک عضو مبادله داشته باشد، مثلا). سوال من را برای برخی از پیش زمینه ها مشاهده کنید.
Benhymers: بله. اصطلاح کپی و جابجایی فقط برای ساده سازی ایجاد کلاسهای مدیریت منابع جدید به صورت کلی در نظر گرفته شده است. برای هر کلاس خاص ، تقریباً مطمئناً مسیری کارآمدتر وجود دارد. این اصطلاح فقط چیزی است که کار می کند و انجام آن سخت است.
تکالیف ، در قلب آن ، دو مرحله است: پاره کردن وضعیت قدیمی شیء و ایجاد وضعیت جدید آن به عنوان کپی از وضعیت برخی از شیء دیگر.
در اصل ، این همان کاری است که ویرانگر و سازنده کپی انجام می دهند ، بنابراین اولین ایده این است که کار را به آنها واگذار کنیم. با این حال ، از آنجا که تخریب نباید شکست بخورد ، در حالی که ساخت و ساز ممکن است ، ما در واقع می خواهیم این کار را به روش دیگر انجام دهیم: ابتدا قسمت سازنده را انجام دهید و اگر این موفقیت موفق شد ، پس بخش مخرب را انجام دهید. اصطلاح کپی و جابجایی راهی برای انجام این کار است: ابتدا با سازنده کپی کلاس برای ایجاد یک شیء موقت تماس می گیرد ، سپس داده های خود را با موقت تعویض می کند ، و سپس به ویرانی موقت اجازه می دهد وضعیت قدیمی را نابود کند. از آنجا که قرار است مبادله () هرگز شکست بخورد ، تنها بخشی که ممکن است شکست بخورد ساخت کپی کردن است. این ابتدا انجام می شود ، و در صورت عدم موفقیت ، هیچ چیز در شی هدف تغییر نخواهد کرد.
در فرم تصفیه شده خود ، کپی و جابجایی با داشتن کپی انجام شده با شروع پارامتر (غیر مرجع) اپراتور انتساب اجرا می شود:
من فکر می کنم ذکر PIMPL به همان اندازه ذکر نسخه ، مبادله و تخریب مهم است. مبادله از نظر جادویی از استثناء نیست. این استثناء است زیرا مبادله نشانگرها از استثناء ایمن است. لازم نیست از PIMPL استفاده کنید ، اما اگر این کار را نکنید ، باید اطمینان حاصل کنید که هر مبادله یک عضو استثناء است. این می تواند کابوسی باشد که این اعضا بتوانند تغییر کنند و وقتی در پشت یک PIMPL پنهان می شوند ، بی اهمیت است. و سپس ، پس از آن هزینه PIMPL می آید. که ما را به این نتیجه می رساند که غالباً ایمنی استثناء هزینه ای در عملکرد دارد.
std :: مبادله (این_ استرینگ ، آن) ضمانت بدون پرتاب را ارائه نمی دهد. این امنیت استثنائی قوی را فراهم می کند ، اما ضمانت بدون پرتاب نیست.
wilhelmtell: در C ++ 03 ، هیچ اشاره ای به استثنائاتی که به طور بالقوه توسط std :: string :: مبادله (که توسط std :: مبادله خوانده می شود) وجود ندارد. در C ++ 0x ، std :: string :: مبادله Noxcept است و نباید استثنائاتی را به وجود آورد.
sbijamesmcnellis خوب ، اما نکته هنوز هم برجسته است: اگر اعضای نوع کلاس دارید باید مطمئن شوید که مبادله آنها یک پرتاب نیست. اگر یک عضو واحد دارید که یک نشانگر است ، پس بی اهمیت است. در غیر این صورت نیست.
wilhelmtell: من فکر کردم که این نقطه مبادله است: هرگز پرتاب نمی شود و همیشه o (1) است (بله ، می دانم ، std :: آرایه.)
در حال حاضر پاسخ های خوبی وجود دارد. من عمدتاً روی آنچه فکر می کنم آنها فاقد آن هستم تمرکز خواهم کرد-توضیحی در مورد "منفی" با اصطلاح کپی و جابجایی.
راهی برای اجرای اپراتور واگذاری از نظر عملکرد مبادله:
ایده اساسی این است که:
بیشترین خطا در اختصاص به یک شیء اطمینان از هرگونه منابعی است که دولت جدید به آن نیاز دارد (به عنوان مثال حافظه ، توصیف کننده)
اگر نسخه ای از مقدار جدید تهیه شود ، می توان آن را قبل از اصلاح وضعیت فعلی شی (یعنی *این) انجام داد ، به همین دلیل RHS با ارزش (یعنی کپی شده) پذیرفته می شود نه مرجع
swapping the state of the local copy rhs and *this is usually relatively easy to do without potential failure/exceptions, given the local copy doesn't need any particular state afterwards (just needs state fit for the destructor to run, much as for an object being moved from in >= C ++ 11)
چه زمانی باید از آن استفاده شود؟(کدام مشکلات را حل می کند [/ایجاد]؟)
هنگامی که شما می خواهید که اختصاص داده شده توسط یک تکلیف که یک استثنا را انجام می دهد ، اعتراض کند ، با فرض اینکه شما می توانید مبادله ای را با ضمانت استثناء قوی بنویسید ، و در حالت ایده آل کسی که نمی تواند شکست بخورد/ پرتاب کند ..
هنگامی که می خواهید یک روش تمیز و آسان برای درک ، برای تعریف اپراتور واگذاری از نظر کارکردهای (ساده تر) کپی ، تعویض و عملکردهای ویرانگر تعریف کنید.
- خودکشی انجام شده به عنوان یک کپی و جابجایی از موارد لبه ای که غالباً نادیده گرفته می شود جلوگیری می کند. ‡
† پرتاب تعویض: به طور کلی امکان تعویض قابل اعتماد اعضای داده وجود دارد که اشیاء توسط نشانگر ردیابی می کنند ، اما اعضای داده های غیر نشانگر که مبادله ای بدون پرتاب ندارند ، یا مبادله ای برای آن باید به عنوان X TMP = LHS انجام شود. lhs = rhs ؛rhs = tmp ؛و ساخت و ساز یا تکلیف ممکن است پرتاب شود ، هنوز هم این امکان را دارد که برخی از اعضای داده را به صورت مبادله و برخی دیگر از بین نبرند. این پتانسیل حتی در مورد C ++ 03 Std :: String به عنوان جیمز در مورد پاسخ دیگر اعمال می شود:
wilhelmtell: در C ++ 03 ، هیچ اشاره ای به استثنائاتی که به طور بالقوه توسط std :: string :: مبادله (که توسط std :: مبادله خوانده می شود) وجود ندارد. در C ++ 0x ، std :: string :: مبادله Noxcept است و نباید استثنائاتی را به وجود آورد.- جیمز مک نلیس 22 دسامبر 10 در 15:24
ore اجرای اپراتور واگذاری که به نظر می رسد هنگام اختصاص از یک شیء مجزا ، به راحتی می تواند برای خودکشی شکست بخورد. در حالی که ممکن است غیرقابل تصور به نظر برسد که کد مشتری حتی سعی در خودآزمایی کند ، می تواند در طی عملیات ALGO روی ظروف ، با x = f (x) نسبتاً به راحتی اتفاق بیفتد. کدی که در آن f (شاید فقط برای برخی از شاخه های #IFDEF باشد) یک ماکرو ala #define f (x) x یا تابعی که یک مرجع به x را بازگرداند ، یا حتی کد (احتمالاً ناکارآمد اما مختصر) مانند x = c1؟X * 2: C2؟x / 2: x ؛). مثلا:
در مورد خودآزمایی ، کد فوق حذف X. P_ ؛، امتیاز P_ را در یک منطقه پشته تازه اختصاص داده شده ، سپس سعی می کند داده های ناخواسته در آن را بخواند (رفتار نامشخص) ، اگر این کار خیلی عجیب و غریب انجام ندهد ، کپی سعی می کند خود را برای همه "T" محروم کند!
id اصطلاح کپی و جابجایی می تواند ناکارآمدی یا محدودیت هایی را به دلیل استفاده از موقت اضافی (هنگامی که پارامتر اپراتور کپی شده است) معرفی کند:
در اینجا ، یک مشتری دستی :: operator = ممکن است بررسی کند که آیا *این قبلاً به همان سرور RHS متصل شده است (شاید در صورت مفید بودن کد "تنظیم مجدد" ارسال کنید) ، در حالی که رویکرد کپی و جابجایی از کپی فراخوانی می کند-سازنده ای که احتمالاً برای باز کردن یک اتصال سوکت مجزا نوشته شده و سپس اصلی را ببندید. این نه تنها می تواند به معنای تعامل شبکه از راه دور به جای یک کپی متغیر در فرآیند ساده باشد ، بلکه می تواند محدودیت های مشتری یا سرور را در منابع سوکت یا اتصالات انجام دهد.(البته این کلاس یک رابط بسیار وحشتناک دارد ، اما این موضوع دیگری است ؛-P).< SPAN> ⁂ اصطلاح کپی و جابجایی می تواند به دلیل استفاده از موقت اضافی (هنگامی که پارامتر اپراتور کپی ساخته شده است) ناکارآمدی یا محدودیت ها را معرفی کند:
مدرسه ی فارکس...
ما را در سایت مدرسه ی فارکس دنبال می کنید
برچسب :
نویسنده : مینا لاکانی
بازدید : <-PostHit->
تاريخ : شنبه
27 اسفند
1401 ساعت: 16:42