حذف رونوشت

در برنامه‌نویسی زبان c++، تکنیک حذف رونوشت یک تکنیک بهینه‌سازی کامپایلر است که رونوشت‌های اضافی اشیا را پاک می‌کند. استاندارد زبان c++ به صورت کلی اجازهٔ پیاده‌سازی‌هایی برای اجرای بهینه‌سازی را می‌دهد که باعث می‌شوند، رفتار قابل مشاهدهٔ برنامهٔ خروجی همانند قانون as-if باشد؛ که یعنی وانمود کردن اینکه برنامه دقیقاً مطابق استاندارد اجرا شده‌است.

استاندارد همچنین شرایطی را توصیف می‌کند که کپی کردن را می‌توان از بین برد حتی اگر این امر باعث تغییر رفتار برنامه شود، رایج‌ترین آن بهینه‌سازی مقدار بازده است. یکی دیگر از بهینه‌سازی‌هایی که به صورت گسترده استفاده می‌شود و در زبان C ++ استاندارد توضیح داده شده‌است، زمانی است که یک شیء موقت از نوع کلاس در یک شی از همان نوع کپی می‌شود. در نتیجه، کپی کردن اولیه معمولاً معادل اولیه سازی مستقیم از نظر عملکردی است، اما در معناشناسی اینگونه نیست. کپی کردن اولیه هنوز نیاز به دسترسی به سازنده کپی دارد. بهینه‌سازی نمی‌تواند برای یک شی موقت که به یک مرجع محدود شده‌است، اعمال شود.

مثال 
#include <iostream>

int n = 0;

struct C {
 explicit C(int) {}
 C(const C&) { ++n; } //سازنده کپی یک اثر جانبی قابل مشاهده دارد
};           // در اینجا یک شی  با مدت زمان ذخیره‌سازی استاتیک تولید می‌شود

int main() {
 C c1(42);   // اولیه سازی مستقیم
 C c2 = C(42); // کپی آغازین

 std::cout << n << std::endl; // اگر نسخه کپی شده باشد 0 و در غیر اینصورت 1 را چاپ کن
}با توجه به استاندارد، بهینه‌سازی مشابهی می‌تواند روی اشیایی که پرت و گرفته می‌شوند، انجام شود؛ اما مشخص نیست که آیا بهینه‌سازی روی هر دو کپی شی پرتاب شده به شی استثنا و کپی شی تعریف شده داخل عبارت استثنا در عبارت catch اعمال می‌شود. همچنین نامشخص است که آیا این بهینه‌سازی فقط روی اشیای موقت اعمال می‌شود یا اشیا نامگذاری شده. در این رابطه به کد زیر توجه کنید:#include <iostream>

struct C {
 C() = default;
 C(const C&) { std::cout << "Hello World!\n"; }
};

void f() {
 C c;
 throw c; // copying the named object c into the exception object.
} // It is unclear whether this copy may be elided (omitted).

int main() {
 try {
  f();
 } catch (C c) { // copying the exception object into the temporary in the
          // exception declaration.
 } // It is also unclear whether this copy may be elided (omitted).
} بنابراین یک کامپایلر سازگار باید برنامه ای تولید کند که "Hello World!" را دو بار چاپ کند. در نسخه فعلی استاندارد C ++، به موضوعاتی پرداخته شده‌است که در اصل اجازه می‌دهد هر دو کپی از شئ نامگذاری شده به شئ استثنا داده شود، و کپی در شیء به عنوان دستگیرهٔ شئ استثا تعریف می‌شود تا حذف شود.

GCC (کلکسیون کامپایلرهای گنو) گزینه ‑fno ‑ elide ‑ را برای غیرفعال کردن کپی فراهم می‌کند. این گزینه برای مشاهده (یا مشاهده نکردن) اثر بهینه‌سازی مقدار بازده یا بهینه‌سازی‌های دیگر در حذف کپی‌ها مفید است. به‌طور کلی توصیه نمی‌شود این بهینه‌سازی مهم را غیرفعال کنید.

بهینه‌سازی مقدار بازگشت 
در زمینهٔ زبان برنامه‌نویسی c++، بهینه‌سازی مقدار بازگشتی یک بهینه‌سازی کامپایلر است که از حذف اشیای موقت که برای نگهداری مقدار بازگشت تابع ساخته شده بودند، استفاده می‌کند.

RVO خصوصاً ابزار قابل توجهی برای تغییر رفتار قابل مشاهده برنامه ناشی از استاندارد C ++ است.

خلاصه 
به‌طور کلی، استاندارد C ++ به یک کامپایلر اجازه می‌دهد تا هرگونه بهینه‌سازی را انجام دهد، مشروط بر اینکه اثر اجرایی حاصل همان رفتار قابل مشاهده را داشته باشد که گویی تمام الزامات استاندارد برآورده شده‌است. معمولاً از آن به عنوان قانون "as-if" یاد می‌شود. اصطلاح بهینه‌سازی مقدار بازگشت به یک بند ویژه در استاندارد C ++ گفته می‌شود که حتی فراتر از قاعده "as-as" می‌رود: یک اجرا ممکن است یک عملیات کپی ناشی از مقدار بازگشتی را حذف کند، حتی اگر سازنده کپی دارای عوارض جانبی باشد.

مثال زیر سناریویی را نشان می‌دهد که در آن پیاده‌سازی ممکن است یکی یا هر دو کپی ساخته شده را حذف کند؛ حتی اگر سازندهٔ کپی یک اثر جانبی قابل مشاهده داشته باشد. (چاپ کردن متن)

اولین کپی که ممکن است حذف شود، جاییست که یک حافظه موقت C و بدون نام می‌تواند در مقدار بازگشتی تابع f کپی شود؛ و دومین کپی که ممکن است حذف شود جاییست که کپی شی موقت بازگشتی توسط f در obj قرار دارد.#include <iostream>

struct C {
 C() = default;
 C(const C&) { std::cout << "A copy was made.\n"; }
};

C F() {
 return C();
}

int main() {
 std::cout << "Hello World!\n";
 C obj = F();
} بسته به نوع کامپایلر و تنظیمات آن کامپایلر، برنامه حاصل ممکن است هر یک از خروجی‌های زیر را نشان دهد:

Hello World!
A copy was made.
A copy was made. Hello World!
A copy was made. Hello World!  بازگشت یک شیء از نوع داخلی از یک تابع معمولاً دارای مقدارکمی هزینه یا بدون هزینه است، زیرا شیء به‌طور معمول در یک ثبات CPU جای می‌گیرد. بازگشت به یک شی بزرگتر از نوع کلاس ممکن است نیاز به کپی گران تری نسبت به یک مکان حافظه به مکان دیگری داشته باشد. برای جلوگیری از این، یک اجرا ممکن است یک شی پنهان را در قالب پشته تماس گیرنده ایجاد کند و آدرس این شی را به تابع منتقل کند. در نهایت مقدار برگشتی این عملکرد در شیء پنهان کپی می‌شود. بنابراین، طبق این کد داریم: struct Data {
 char bytes[16];
};

Data F() {
 Data result = {};
 // generate result
 return result;
}

int main() {
 Data d = F();
} ممکن است کد معادل این را تولید کند: struct Data {
 char bytes[16];
};

Data* F(Data* _hiddenAddress) {
 Data result = {};
 // copy result into hidden object
 *_hiddenAddress = result;
 return _hiddenAddress;
}

int main() {
 Data _hidden;      // create hidden object
 Data d = *F(&_hidden); // copy the result into d
} که باعث می‌شود شیء Data دو بار کپی شود.

در اولین مراحل تکاملc++، عدم توانایی زبان برای بازگرداندن مؤثر یک شی از نوع کلاس از یک تابع ضعف محسوب می‌شد. حدود ۱۹۹۱، والتر برایت یک تکنیک برای حداقل کردن کپی‌ها پیاده‌سازی کرد؛ که به صورت مؤثر شی پنهان شده و شی نامگذاری شده را با یک شی که برای نگهداری نتیجه به کار می‌رفت جابه‌جا می‌کرد. struct Data {
 char bytes[16];
};

void F(Data* p) {
 // generate result directly in *p
}

int main() {
 Data d;
 F(&d);
} برایت پیاده‌سازی این بهینه‌سازی را در کامپایلر Zortech (c++) انجام داد. این تکنیک ویژه بعدها، با اشاره به این نکته که کپی‌های یک شی نامگذاری شده حذف می‌شوند، بهینه‌سازی مقدار بازگشتی نامگذاری شده نام گرفت.

پشتیبانی کامپایلر 
بهینه‌سازی مقدار بازگشتی در اکثر کامپایلرها پشتیبانی می‌شود. اما ممکن است شرایطی وجود داشته باشد که کامپایلر قادر به اجرای بهینه‌سازی نباشد. یک نمونهٔ رایج آن زمانی است که یک تابع ممکن است اشیای نامگذاری شدهٔ مختلفی را بسته به مسیر اجرایش برگرداند: #include <string>
std::string F(bool cond = false) {
 std::string first("first");
 std::string second("second");
 // the function may return one of two named objects
 // depending on its argument. RVO might not be applied
 return cond ? first : second;
}

int main() {
 std::string result = F();
}

جستارهای وابسته 
 Copy elision on cppreference.com

منابع 

بهینه‌سازی کامپایلر
سی++