از ارزش عبور کنید. انتقال پارامترها توسط مرجع و مقدار. تنظیمات پیش فرض

بنابراین، اجازه دهید Factorial(n) تابعی برای محاسبه فاکتوریل یک عدد n باشد. سپس، با توجه به اینکه "می دانیم" فاکتوریل 1 1 است، می توانیم زنجیره زیر را بسازیم:

فاکتوریال(4)=فاکتوریال(3)*4

فاکتوریال(3)=فاکتوریال(2)*3

فاکتوریال(2)=فاکتوریال(1)*2

اما، اگر ما یک شرط پایانی نداشتیم که وقتی n=1 تابع فاکتوریال باید 1 را برگرداند، آنگاه چنین زنجیره نظری هرگز به پایان نمی‌رسید و این می‌توانست یک خطای Call Stack Overflow باشد - فراخوانی stack overflow. برای درک اینکه پشته فراخوانی چیست و چگونه می تواند سرریز شود، اجازه دهید به اجرای بازگشتی تابع خود نگاه کنیم:

تابع فاکتوریل (n: عدد صحیح): LongInt;

اگر n=1 سپس

فاکتوریل:=فاکتوریال(n-1)*n;

پایان؛

همانطور که می بینیم، برای اینکه زنجیره به درستی کار کند، قبل از هر فراخوانی تابع بعدی به خودش، باید همه متغیرهای محلی را در جایی ذخیره کرد تا وقتی زنجیره معکوس شد، نتیجه درست باشد (مقدار محاسبه شده). فاکتوریل n-1 در n ضرب می شود). در مورد ما، هر بار که تابع فاکتوریل از خود فراخوانی می شود، تمام مقادیر متغیر n باید ذخیره شوند. ناحیه ای که متغیرهای محلی یک تابع در هنگام فراخوانی بازگشتی خود در آن ذخیره می شوند پشته تماس نامیده می شود. البته این پشته بی نهایت نیست و در صورت ایجاد نادرست فراخوانی های بازگشتی می تواند تمام شود. محدود بودن تکرارهای مثال ما با این واقعیت تضمین می شود که وقتی n=1 فراخوانی تابع متوقف می شود.

انتقال پارامترها بر اساس مقدار و با مرجع

تا به حال نمی توانستیم مقدار را در زیربرنامه تغییر دهیم پارامتر واقعی(به عنوان مثال، پارامتری که هنگام فراخوانی زیربرنامه مشخص می شود)، و در برخی از وظایف برنامه، این کار راحت است. بیایید روال Val را به یاد بیاوریم که مقدار دو پارامتر واقعی خود را به طور همزمان تغییر می دهد: اولی پارامتری است که در آن مقدار تبدیل شده متغیر رشته نوشته می شود و دومی پارامتر Code است که در آن تعداد موارد اشتباه وجود دارد. کاراکتر در صورت شکست در هنگام تبدیل نوع قرار می گیرد. آن ها هنوز مکانیزمی وجود دارد که توسط آن یک برنامه فرعی می تواند پارامترهای واقعی را تغییر دهد. این به لطف روش های مختلف انتقال پارامترها امکان پذیر است. بیایید نگاهی دقیق تر به این روش ها بیندازیم.

برنامه نویسی در پاسکال

عبور پارامترها بر اساس مقدار

اساساً اینگونه است که ما تمام پارامترها را به روال خود منتقل می کنیم. مکانیسم به شرح زیر است: هنگامی که یک پارامتر واقعی مشخص می شود، مقدار آن در ناحیه حافظه ای که زیربرنامه در آن قرار دارد کپی می شود و سپس، پس از اتمام کار تابع یا رویه، این ناحیه پاک می شود. به طور کلی، در حالی که یک زیربرنامه در حال اجرا است، دو نسخه از پارامترهای آن وجود دارد: یکی در محدوده برنامه فراخوانی، و دوم در محدوده تابع.

با این روش انتقال پارامترها، فراخوانی زیربرنامه زمان بیشتری می برد، زیرا علاوه بر خود فراخوانی، کپی کردن تمام مقادیر تمام پارامترهای واقعی ضروری است. اگر حجم زیادی از داده به زیربرنامه ارسال شود (مثلاً آرایه ای با تعداد عناصر زیاد)، زمان لازم برای کپی کردن داده ها در منطقه محلی می تواند قابل توجه باشد و این باید در هنگام توسعه برنامه ها در نظر گرفته شود. یافتن گلوگاه در عملکرد خود

با این روش انتقال، پارامترهای واقعی را نمی توان توسط زیر روال تغییر داد، زیرا تغییرات تنها بر یک ناحیه محلی جدا شده تأثیر می گذارد، که پس از تکمیل عملکرد یا رویه آزاد می شود.

انتقال پارامترها توسط مرجع

با این روش، مقادیر پارامترهای واقعی در زیربرنامه کپی نمی‌شوند، بلکه آدرس‌های موجود در حافظه (پیوندها به متغیرها) که در آن قرار دارند، منتقل می‌شوند. در این حالت، زیربرنامه در حال حاضر مقادیری را تغییر می دهد که در محدوده محلی نیستند، بنابراین همه تغییرات برای برنامه فراخوانی قابل مشاهده خواهد بود.

برای نشان دادن اینکه یک آرگومان باید با مرجع ارسال شود، کلمه کلیدی var قبل از اعلان آن اضافه می شود:

Procedure getTwoRandom(var n1, n2:Integer; range: Integer);

n1: = تصادفی (محدوده);

n2: = تصادفی (محدوده); پایان ؛

var rand1, rand2: عدد صحیح;

شروع getTwoRandom(rand1,rand2,10); WriteLn(rand1); WriteLn(rand2);

پایان.

در این مثال، ارجاع به دو متغیر به عنوان پارامترهای واقعی به رویه getTwoRandom ارسال می شود: rand1 و rand2. سومین پارامتر واقعی (10) توسط مقدار ارسال می شود. رویه با استفاده از پارامترهای رسمی می نویسد

روش های برنامه نویسی با استفاده از رشته ها

هدف از انجام کار آزمایشگاهی : یادگیری روش ها در زبان سی شارپ، قوانین کار با داده های کاراکتر و جزء ListBox. برنامه ای برای کار با رشته ها بنویسید.

مواد و روش ها

متد یک عنصر کلاس است که حاوی کد برنامه است. این روش دارای ساختار زیر است:

[ویژگی‌ها] [مشخص‌کننده‌ها] نام نوع ([پارامترها])

بدن روش;

ویژگی ها دستورالعمل های ویژه ای برای کامپایلر در مورد ویژگی های یک متد هستند. ویژگی ها به ندرت مورد استفاده قرار می گیرند.

واجد شرایط کلمات کلیدی هستند که اهداف مختلفی را دنبال می کنند، به عنوان مثال:

· تعیین در دسترس بودن یک متد برای کلاس های دیگر:

o خصوصی- روش فقط در این کلاس در دسترس خواهد بود

o حفاظت شده- این روش برای کلاس های کودک نیز در دسترس خواهد بود

o عمومی- متد برای هر کلاس دیگری که بتواند به این کلاس دسترسی داشته باشد در دسترس خواهد بود

نشان دادن در دسترس بودن یک متد بدون ایجاد کلاس

· نوع تنظیم

نوع، نتیجه ای را که متد برمی گرداند تعیین می کند: این می تواند هر نوع موجود در سی شارپ باشد، و همچنین در صورت عدم نیاز به نتیجه، کلمه کلیدی void باشد.

نام متد، شناسه‌ای است که برای فراخوانی متد استفاده می‌شود. الزامات یکسانی برای یک شناسه مانند نام متغیرها اعمال می شود: می تواند از حروف، اعداد و زیرخط تشکیل شود، اما نمی تواند با یک عدد شروع شود.

پارامترها لیستی از متغیرها هستند که در هنگام فراخوانی به یک متد ارسال می شوند. هر پارامتر از یک نوع و نام متغیر تشکیل شده است. پارامترها با کاما از هم جدا می شوند.

بدنه یک متد یک کد برنامه معمولی است، با این تفاوت که نمی تواند شامل تعاریف سایر متدها، کلاس ها، فضاهای نام و غیره باشد. اگر یک متد باید نتیجه ای را برگرداند، کلمه کلیدی بازگشتی باید در انتها با مقدار بازگشتی وجود داشته باشد. . اگر بازگرداندن نتایج ضروری نیست، استفاده از کلمه کلیدی بازگشت ضروری نیست، اگرچه مجاز است.

نمونه ای از روشی که یک عبارت را ارزیابی می کند:

عمومی دو کالک (double a، double b، double c)

بازگشت Math.Sin(a) * Math.Cos(b);

k double = Math.Tan(a * b);

بازگشت k * Math.Exp(c / k);

روش اضافه بار

زبان سی شارپ به شما این امکان را می دهد که چندین متد با نام های مشابه اما پارامترهای متفاوت ایجاد کنید. کامپایلر هنگام ساخت برنامه به طور خودکار مناسب ترین روش را انتخاب می کند. به عنوان مثال، می توانید دو روش جداگانه برای افزایش یک عدد به توان بنویسید: یک الگوریتم برای اعداد صحیح و دیگری برای اعداد واقعی استفاده می شود:

///

/// X را به توان Y برای اعداد صحیح محاسبه کنید

///

int خصوصی Pow(int X، int Y)

///

/// X را به توان Y برای اعداد واقعی محاسبه کنید

///

دو پاو خصوصی (X دوبل، Y دوبل)

بازگشت Math.Exp(Y * Math.Log(Math.Abs(X)));

در غیر این صورت (Y == 0)

این کد به همین ترتیب فراخوانی می شود ، تنها تفاوت در پارامترها است - در حالت اول ، کامپایلر متد Pow را با پارامترهای عدد صحیح فراخوانی می کند و در حالت دوم - با پارامترهای واقعی:

تنظیمات پیش فرض

زبان سی شارپ که با نسخه 4.0 (Visual Studio 2010) شروع می شود، به شما امکان می دهد مقادیر پیش فرض را برای برخی از پارامترها تنظیم کنید، به طوری که هنگام فراخوانی یک متد می توانید برخی از پارامترها را حذف کنید. برای انجام این کار، هنگام اجرای روش، پارامترهای مورد نیاز باید مستقیماً در لیست پارامترها یک مقدار تعیین کنند:

Private void GetData(int Number, int Optional = 5 )

Console.WriteLine("Number: (0)", Number);

Console.WriteLine("اختیاری: (0)"، اختیاری);

در این حالت می توانید متد را به صورت زیر فراخوانی کنید:

GetData(10, 20);

در حالت اول از آنجایی که به صراحت مشخص شده است پارامتر Option برابر با 20 و در حالت دوم برابر با 5 خواهد بود زیرا به صراحت مشخص نشده است و کامپایلر مقدار پیش فرض را می گیرد.

پارامترهای پیش‌فرض را فقط می‌توان در سمت راست فهرست پارامترها تنظیم کرد؛ برای مثال، امضای چنین روشی توسط کامپایلر پذیرفته نمی‌شود:

Private void GetData(int Optional = 5 ، شماره int)

هنگامی که پارامترها به روش معمولی (بدون کلیدواژه های ref و out اضافی) به یک متد ارسال می شوند، هر گونه تغییر در پارامترهای درون متد بر مقدار آن در برنامه اصلی تأثیر نمی گذارد. فرض کنید روش زیر را داریم:

خلأ خصوصی Calc (شماره int)

مشاهده می شود که در داخل متد متغیر Number که به عنوان پارامتر ارسال شده بود تغییر می کند. بیایید سعی کنیم متد را فراخوانی کنیم:

Console.WriteLine(n);

عدد 1 روی صفحه ظاهر می شود، یعنی با وجود تغییر متغیر در متد Calc، مقدار متغیر در برنامه اصلی تغییر نکرده است. این به این دلیل است که وقتی یک متد فراخوانی می شود، a کپی 🀄متغیر گذشت، این متغیر است که روش تغییر می کند. هنگامی که روش پایان می یابد، ارزش کپی ها از بین می رود. این روش ارسال یک پارامتر نامیده می شود عبور از ارزش.

برای اینکه متدی بتواند متغیری را که به آن ارسال می‌شود تغییر دهد، باید با کلمه کلیدی ref ارسال شود - باید هم در امضای متد باشد و هم در هنگام فراخوانی:

Private void Calc (رجع شماره int)

Console.WriteLine(n);

در این حالت، عدد 10 روی صفحه ظاهر می شود: تغییر مقدار در روش بر برنامه اصلی نیز تأثیر می گذارد. این انتقال روش نامیده می شود عبور از مرجع، یعنی این دیگر یک کپی نیست که منتقل می شود، بلکه اشاره ای به یک متغیر واقعی در حافظه است.

اگر روشی از متغیرها با ارجاع فقط برای برگرداندن مقادیر استفاده می کند و اهمیتی ندارد که در ابتدا چه چیزی در آنها بوده است، در این صورت نمی توانید چنین متغیرهایی را مقداردهی اولیه کنید، بلکه آنها را با کلمه کلیدی out ارسال کنید. کامپایلر درک می کند که مقدار اولیه متغیر مهم نیست و از عدم مقداردهی اولیه شکایت نمی کند:

محاسبات خلأ خصوصی (تعداد ورودی خروجی)

int n; // ما چیزی را تعیین نمی کنیم!

نوع داده رشته ای

زبان سی شارپ از نوع رشته برای ذخیره رشته ها استفاده می کند. برای اعلام (و به عنوان یک قاعده، بلافاصله مقداردهی اولیه) یک متغیر رشته، می توانید کد زیر را بنویسید:

string a = "متن";

string b = "strings";

می توانید یک عملیات جمع را روی خطوط انجام دهید - در این صورت، متن یک خط به متن خط دیگر اضافه می شود:

رشته c = a + " " + b; // نتیجه: متن رشته

نوع رشته در واقع یک نام مستعار برای کلاس String است که به شما امکان می دهد تعدادی عملیات پیچیده تر را روی رشته ها انجام دهید. برای مثال، متد IndexOf می‌تواند زیر رشته‌ای را در یک رشته جستجو کند و متد Substring بخشی از رشته را با طول مشخصی که از یک موقعیت مشخص شروع می‌شود، برمی‌گرداند:

string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int index = a.IndexOf("OP"); // نتیجه: 14 (شمارش از 0)

رشته b = a.Substring(3, 5); // نتیجه: DEFGH

اگر نیاز به اضافه کردن کاراکترهای خاص به یک رشته دارید، می توانید این کار را با استفاده از دنباله های فرار که با بک اسلش شروع می شوند انجام دهید:

جزء ListBox

جزء کادر فهرستلیستی است که عناصر آن با استفاده از صفحه کلید یا ماوس انتخاب می شوند. لیست عناصر توسط ویژگی مشخص می شود موارد. آیتم ها عنصری هستند که ویژگی ها و روش های خاص خود را دارند. مواد و روش ها اضافه کردن, RemoveAtو درج کنیدبرای افزودن، حذف و درج عناصر استفاده می شود.

یک شی موارداشیاء موجود در لیست را ذخیره می کند. شی می تواند هر کلاسی باشد - داده های کلاس برای نمایش به یک نمایش رشته با روش ToString تبدیل می شود. در مورد ما، رشته ها به عنوان اشیا عمل خواهند کرد. با این حال، از آنجایی که شی Items اشیاء ریخته‌شده به شی تایپ را ذخیره می‌کند، قبل از استفاده از آن باید آنها را به نوع اصلی‌شان برگردانید، در مورد رشته ما:

string a = (string)listBox1.Items;

برای تعیین تعداد عنصر انتخاب شده، از ویژگی استفاده کنید SelectedIndex.

زمانی که برنامه نویسی را در ++C شروع کردم و به طور فشرده کتاب ها و مقالات را مطالعه کردم، همیشه به همین توصیه برخورد کردم: اگر ما نیاز به ارسال یک شی به تابعی داریم که نباید در تابع تغییر کند، باید همیشه آن را ارسال کنیم. با ارجاع به یک ثابت(PPSK)، به جز مواردی که باید نوع اولیه یا ساختاری مشابه اندازه آنها را پاس کنیم. زیرا برای بیش از 10 سال برنامه نویسی در C ++، من اغلب با این توصیه مواجه شده ام (و خودم بیش از یک بار آن را داده ام)، مدت هاست که در من "جذب" شده است - من به طور خودکار همه آرگومان ها را با ارجاع به یک ثابت منتقل می کنم. . اما زمان می گذرد و 7 سال از زمانی که ما C++11 را با معنای حرکتی آن در اختیار داشتیم، می گذرد، که در ارتباط با آن، صداهای بیشتری را می شنوم که عقاید خوب قدیمی را زیر سوال می برند. بسیاری شروع به استدلال کرده اند که عبور از ارجاع به یک ثابت امری مربوط به گذشته است و اکنون ضروری است عبور از ارزش(PPZ). آنچه پشت این گفتگوها نهفته است و همچنین اینکه چه نتیجه ای می توانیم از همه اینها بگیریم، می خواهم در این مقاله به آن بپردازم.

حکمت کتاب

برای اینکه بفهمیم باید به چه قاعده ای پایبند باشیم، پیشنهاد می کنم به کتاب مراجعه کنیم. کتاب ها منبع عالی اطلاعاتی هستند که ما مجبور به پذیرش آن نیستیم، اما مطمئناً ارزش شنیدن آن را دارد. و ما با تاریخ شروع خواهیم کرد، با ریشه ها. من متوجه نخواهم شد که اولین معذرت خواهی PPSC چه کسی بود، فقط به عنوان مثال کتابی را که شخصاً بیشترین تأثیر را روی من در مورد استفاده از PPSC داشت، مثال می زنم.

مایرز

خوب، در اینجا ما یک کلاس داریم که در آن همه پارامترها با مرجع ارسال می شوند، آیا این کلاس مشکلی دارد؟ متأسفانه وجود دارد و این مشکل در ظاهر نهفته است. ما 2 موجودیت تابعی در کلاس خود داریم: اولی یک مقدار را در مرحله ایجاد شی می گیرد و دومی به شما اجازه می دهد مقداری را که قبلاً تنظیم شده است تغییر دهید. ما دو نهاد داریم، اما چهار تابع. حال تصور کنید که ما می توانیم نه 2 موجودیت مشابه، بلکه 3، 5، 6 داشته باشیم، پس چه؟ سپس با نفخ شدید کد مواجه خواهیم شد. بنابراین، برای اینکه انبوهی از توابع ایجاد نشود، پیشنهادی برای کنار گذاشتن پیوندها در پارامترها وجود داشت:

قالب دارنده کلاس ( عمومی: صریح دارنده (مقدار T): m_Value(move(value)) () void setValue(T value) (m_Value = move(value); ) const T& value() const noexception (بازگردان m_Value؛ ) خصوصی: T m_Value; );

اولین مزیتی که بلافاصله توجه شما را جلب می کند این است که کد به میزان قابل توجهی کمتر است. به دلیل حذف const و & (البته حرکت را اضافه کردند) حتی کمتر از نسخه اول وجود دارد. اما همیشه به ما آموخته‌اند که گذر از مرجع مولدتر از عبور از ارزش است! قبل از C++11 اینطور بود و هنوز هم همینطور است، اما حالا اگر به این کد نگاه کنیم، می بینیم که اینجا بیشتر از نسخه اول کپی وجود ندارد. مشروط بر اینکه T دارای سازنده حرکت باشد. آن ها خود PPSC سریعتر از PPZ بوده و خواهد بود، اما کد به نحوی از مرجع ارسال شده استفاده می کند و اغلب این آرگومان کپی می شود.

با این حال، این تمام ماجرا نیست. برخلاف گزینه اول که فقط کپی داریم، در اینجا حرکت را هم اضافه می کنیم. اما جابجایی یک عملیات ارزان است، درست است؟ در مورد این موضوع، کتاب مایرز مورد بررسی ما نیز فصلی دارد («مورد 29») که با عنوان: «فرض کنید عملیات جابجایی وجود ندارد، ارزان نیست و استفاده نمی‌شود». ایده اصلی باید از عنوان مشخص باشد، اما اگر جزئیات می‌خواهید، حتماً آن را بخوانید - من روی آن تمرکز نمی‌کنم.

در اینجا مناسب است که یک تحلیل مقایسه ای کامل از روش اول و آخر انجام دهیم، اما من نمی خواهم از کتاب منحرف شوم، بنابراین تحلیل را به بخش های دیگر موکول می کنیم و در اینجا به بررسی استدلال های اسکات ادامه می دهیم. بنابراین، جدا از این واقعیت که گزینه سوم به وضوح کوتاهتر از گزینه دوم است، اسکات مزیت PPZ نسبت به PPSC در کدهای مدرن را در چه چیزی می بیند؟

او آن را در این واقعیت می بیند که در مورد عبور از یک مقدار، i.e. برخی به این شکل تماس می گیرند: Holder holder(string("me")); ، گزینه با PPSC به ما کپی می دهد و گزینه با PPZ به ما حرکت می دهد. از طرف دیگر، اگر انتقال به این صورت باشد: Holder holder(someLvalue); ، سپس PPZ به دلیل این واقعیت که هم کپی و هم جابجایی را انجام می دهد قطعاً ضرر می کند ، در حالی که در نسخه با PPSC فقط یک کپی وجود دارد. آن ها معلوم می‌شود که PPZ، اگر صرفاً کارایی را در نظر بگیریم، نوعی سازش بین مقدار کد و پشتیبانی «کامل» (از طریق &&) برای معنای‌شناسی حرکت است.

به همین دلیل اسکات توصیه خود را با دقت بیان کرد و آن را با دقت تبلیغ کرد. حتی به نظرم می رسید که او آن را با اکراه و گویی تحت فشار مطرح کرده است: او نمی تواند بحث هایی را در مورد این موضوع در کتاب نیاورد، زیرا ... به طور گسترده ای مورد بحث قرار گرفت و اسکات همیشه گردآورنده تجربیات جمعی بود. علاوه بر این، او استدلال های بسیار کمی در دفاع از PPZ ارائه می کند، اما بسیاری از دلایلی را ارائه می دهد که این "تکنیک" را زیر سوال می برد. ما در بخش‌های بعدی به استدلال‌های او علیه آن‌ها نگاه خواهیم کرد، اما در اینجا به طور خلاصه استدلالی را که اسکات در دفاع از PPP مطرح می‌کند، تکرار می‌کنیم. "اگر جسم از حرکت پشتیبانی کند و ارزان باشد"): به شما امکان می دهد هنگام ارسال یک عبارت rvalue به عنوان آرگومان تابع از کپی برداری خودداری کنید. اما به اندازه کافی کتاب مایرز عذاب آور است، اجازه دهید به سراغ کتاب دیگری برویم.

به هر حال، اگر کسی کتاب را خوانده باشد و تعجب کند که من گزینه ای را در اینجا درج نمی کنم که مایر آن را ارجاعات جهانی می نامد - که اکنون به عنوان مراجع ارسال شناخته می شود - به راحتی توضیح داده می شود. من فقط PPZ و PPSC را در نظر دارم، زیرا ... معرفی توابع الگو برای متدهایی که الگو نیستند، فقط به خاطر پشتیبانی از عبور با مرجع هر دو نوع (rvalue/lvalue) به نظر من بد است. ناگفته نماند که کد متفاوت می شود (بدون ثبات بیشتر) و مشکلات دیگری را به همراه دارد.

Josattis و شرکت

آخرین کتابی که ما به آن نگاه خواهیم کرد، "C++ Templates" است، که همچنین جدیدترین کتاب از تمام کتاب های ذکر شده در این مقاله است. در پایان سال 2017 منتشر شد (و سال 2018 در داخل کتاب ذکر شده است). برخلاف سایر کتاب‌ها، این کتاب کاملاً به الگوها اختصاص دارد و نه توصیه (مانند مایرز) یا به طور کلی ++C، مانند استروستروپ. بنابراین، مزایا / معایب در اینجا از نقطه نظر قالب های نوشتن در نظر گرفته می شود.

کل فصل 7 به این موضوع اختصاص یافته است که عنوان شیوای آن "بر اساس ارزش یا با مرجع؟" است. در این فصل، نویسندگان به طور کاملاً مختصر اما مختصر همه روش‌های انتقال را با تمام جوانب مثبت و منفی آن‌ها توصیف می‌کنند. تحلیلی از اثربخشی عملاً در اینجا ارائه نشده است و مسلم است که PPSC سریعتر از PPZ خواهد بود. اما با همه اینها، در پایان فصل نویسندگان استفاده از PPP پیش فرض را برای توابع الگو توصیه می کنند. چرا؟ زیرا با استفاده از پیوند، پارامترهای قالب به طور کامل نمایش داده می شوند و بدون پیوند "تخریب" می شوند که تأثیر مفیدی بر پردازش آرایه ها و حروف الفبای رشته ها دارد. نویسندگان بر این باورند که اگر برای برخی از انواع PPP بی اثر باشد، می توانید همیشه از std::ref و std::cref استفاده کنید. این یک توصیه است، صادقانه بگویم، آیا شما افراد زیادی را دیده اید که می خواهند از توابع بالا استفاده کنند؟

آنها در مورد PPSC چه توصیه ای دارند؟ آنها توصیه می کنند از PPSC زمانی که عملکرد حیاتی است یا موارد دیگر وجود دارد استفاده کنید وزیندلایل عدم استفاده از PPP البته، ما در اینجا فقط در مورد کد دیگ بخار صحبت می کنیم، اما این توصیه مستقیماً با تمام آنچه برنامه نویسان برای یک دهه آموزش داده اند، در تضاد است. این فقط توصیه به در نظر گرفتن PPP به عنوان یک جایگزین نیست - نه، این توصیه ای برای تبدیل PPSC به یک جایگزین است.

این سفر کتاب ما را به پایان می رساند، زیرا ... من هیچ کتاب دیگری نمی شناسم که در این مورد به آنها مراجعه کنیم. بریم سراغ فضای رسانه ای دیگه.

خرد شبکه

زیرا ما در عصر اینترنت زندگی می کنیم، پس نباید تنها به خرد کتاب تکیه کنید. علاوه بر این، بسیاری از نویسندگانی که قبلاً کتاب می نوشتند، اکنون به سادگی وبلاگ می نویسند و کتاب ها را رها کرده اند. یکی از این نویسندگان، هرب ساتر است، که در می 2013 مقاله‌ای را در وبلاگ خود با عنوان «راه حل GotW #4: مکانیک کلاس» منتشر کرد، که اگرچه کاملاً به مشکلی که ما پوشش می‌دهیم اختصاص داده نشده است، اما همچنان به آن اشاره می‌کند.

بنابراین، در نسخه اصلی مقاله، ساتر به سادگی این حکمت قدیمی را تکرار کرد: "پارامترها را با ارجاع به یک ثابت عبور دهید"، اما ما دیگر این نسخه از مقاله را نخواهیم دید، زیرا مقاله حاوی توصیه مخالف است: اگرپارامتر همچنان کپی می‌شود، سپس آن را با مقدار ارسال می‌کنیم.» باز هم "اگر" بدنام. چرا ساتر مقاله را تغییر داد و من چگونه از آن مطلع شدم؟ از نظرات. نظرات مقاله او را بخوانید؛ اتفاقاً از خود مقاله جالب تر و مفیدتر هستند. درست است، پس از نوشتن مقاله، ساتر در نهایت نظر خود را تغییر داد و او دیگر چنین توصیه ای نمی کند. تغییر نظر را می توان در سخنرانی او در CppCon در سال 2014 یافت: «بازگشت به اصول! ملزومات سبک مدرن C++». حتما نگاه کنید، به لینک اینترنتی بعدی می رویم.

و بعد ما منبع اصلی برنامه نویسی قرن بیست و یکم را داریم: StackOverflow. یا بهتر است بگوییم پاسخ، با تعداد واکنش های مثبت بیش از 1700 در زمان نوشتن این مقاله. سوال این است: اصطلاح کپی و تعویض چیست؟ ، و همانطور که عنوان نشان می دهد، کاملاً در مورد موضوع مورد نظر ما نیست. اما نویسنده در پاسخ به این سوال به موضوعی هم می پردازد که مورد علاقه ماست. او همچنین استفاده از PPZ را توصیه می کند "اگر استدلال به هر حال کپی شود" (وقت آن است که یک مخفف برای این نیز معرفی کنیم، به خدا). و به طور کلی، این توصیه در چارچوب پاسخ خود و اپراتور مورد بحث در آنجا کاملاً مناسب به نظر می رسد، اما نویسنده این اختیار را به خود می دهد که چنین توصیه ای را به صورت گسترده تر و نه فقط در این مورد خاص ارائه دهد. علاوه بر این، او از تمام نکاتی که قبلاً صحبت کردیم فراتر رفته و خواستار انجام این کار حتی در کد C++03 است! چه چیزی نویسنده را وادار به چنین نتیجه گیری کرد؟

ظاهراً نویسنده پاسخ، الهام اصلی را از مقاله ای از نویسنده کتاب دیگر و توسعه دهنده پاره وقت Boost.MPL - دیو آبراهامز - گرفته است. این مقاله "سرعت می خواهید؟ از ارزش عبور کن.» ، و در اوت 2009 منتشر شد، i.e. 2 سال قبل از پذیرش C++11 و معرفی معناشناسی حرکت. مانند موارد قبلی، من به خواننده توصیه می کنم که مقاله را به تنهایی بخواند، اما من استدلال های اصلی را ارائه می کنم (در واقع فقط یک استدلال وجود دارد) که دیو به نفع PPZ ارائه می دهد: شما باید از PPZ استفاده کنید. ، زیرا بهینه سازی "پرش از کپی" به خوبی با آن کار می کند (کپی elision) که در PPSC وجود ندارد. اگر نظرات مقاله را بخوانید، می بینید که توصیه هایی که او تبلیغ می کند جهانی نیست، که خود نویسنده در پاسخ به انتقادات مفسران آن را تأیید می کند. با این حال، مقاله حاوی توصیه صریح (راهنما) برای استفاده از PPP است، اگر استدلال به هر حال کپی شود. به هر حال، اگر کسی علاقه مند است، می تواند مقاله “سرعت می خواهید؟ (همیشه) از ارزش عبور نکنید.» . همانطور که عنوان باید نشان دهد، این مقاله پاسخی به مقاله دیو است، پس اگر مقاله اول را خواندید، حتما این مقاله را نیز بخوانید!

متأسفانه (خوشبختانه برای برخی)، چنین مقالات و (حتی بیشتر) پاسخ های محبوب در سایت های محبوب باعث استفاده گسترده از تکنیک های مشکوک (نمونه ای بی اهمیت) می شود، فقط به این دلیل که این نیاز به نوشتن کمتری دارد، و عقیده قدیمی دیگر تزلزل ناپذیر نیست - اگر شما را به دیوار هل دادند، همیشه می توانید به «آن نصیحت رایج» مراجعه کنید. اکنون به شما پیشنهاد می کنم با توصیه هایی برای نوشتن کد با منابع مختلف آشنا شوید.

زیرا از آنجایی که استانداردها و توصیه های مختلف اکنون به صورت آنلاین نیز ارسال شده است، تصمیم گرفتم این بخش را به عنوان "خرد شبکه" طبقه بندی کنم. بنابراین، در اینجا می خواهم در مورد دو منبع صحبت کنم که هدف از آنها بهبود کد برنامه نویسان ++C با ارائه نکات (راهنمای) در مورد نحوه نوشتن همین کد است.

اولین مجموعه قوانینی که می‌خواهم در نظر بگیرم آخرین چیزی بود که من را مجبور کرد این مقاله را انتخاب کنم. این مجموعه بخشی از ابزار clang-tidy است و خارج از آن وجود ندارد. مانند هر چیزی که مربوط به clang است، این ابزار بسیار محبوب است و قبلاً با CLion و Resharper C++ یکپارچه شده است (من با آن برخورد کردم). بنابراین، clang-tydy حاوی یک قاعده مدرنیزاسیون عبور از ارزش است که روی سازنده هایی کار می کند که آرگومان ها را از طریق PPSC می پذیرند. این قانون پیشنهاد می کند که PPSC را با PPZ جایگزین کنیم. ضمناً در زمان نگارش مقاله، شرح این قاعده حاوی این نکته است که این قاعده خدا حافظفقط برای سازندگان کار می کند، اما آنها (آنها چه کسانی هستند؟) با کمال میل از کسانی که این قانون را به سایر نهادها تعمیم می دهند، کمک خواهند پذیرفت. در آنجا، در توضیحات، پیوندی به مقاله دیو وجود دارد - واضح است که پاها از کجا آمده اند.

در نهایت، برای به پایان رساندن این بررسی از خرد و نظرات معتبر دیگران، به شما پیشنهاد می‌کنم به دستورالعمل‌های رسمی برای نوشتن کد C++ نگاهی بیندازید: C++ Core Guidelines، که ویراستاران اصلی آن Herb Sutter و Bjarne Stroustrup هستند (بد نیست، درست است؟). بنابراین، این توصیه ها حاوی قانون زیر است: "برای پارامترهای "in"، انواع ارزان کپی شده را بر اساس مقدار و بقیه را با ارجاع به const ارسال کنید، که کاملاً حکمت قدیمی را تکرار می کند: PPSK در همه جا و PPP برای اشیاء کوچک. این نکته چندین جایگزین را که باید در نظر گرفته شود، تشریح می کند. در صورتی که ارسال آرگومان نیاز به بهینه سازی داشته باشد. اما PPZ در لیست جایگزین ها گنجانده نشده است!

از آنجایی که من هیچ منبع دیگری در خور توجه ندارم، پیشنهاد می کنم به تحلیل مستقیم هر دو روش انتقال برویم.

تحلیل و بررسی

کل متن قبلی برای من تا حدودی غیرعادی نوشته شده است: من نظرات دیگران را ارائه می کنم و حتی سعی می کنم نظرات خودم را بیان نکنم (می دانم که بد می شود). عمدتاً به دلیل این واقعیت است که نظرات دیگران و هدف من این بود که مروری کوتاه بر آنها داشته باشم، بررسی دقیق برخی از استدلال هایی را که در سایر نویسندگان یافتم به تعویق انداختم. در این بخش به مراجع و اظهار نظر نمی پردازم، در اینجا به بررسی مزایا و معایب عینی PPSC و PPZ می پردازیم که با برداشت ذهنی من چاشنی خواهد شد. البته برخی از مواردی که قبلاً مطرح شد تکرار خواهد شد، اما افسوس که ساختار این مقاله اینگونه است.

آیا PPP مزیتی دارد؟

بنابراین، قبل از بررسی ادله موافق و مخالف، پیشنهاد می کنم به این نکته توجه کنیم که چه و در چه مواردی مزیتی که عبور از ارزش به ما می دهد. فرض کنید کلاسی مانند این داریم:

Class CopyMover ( عمومی: void setByValuer(Accounter byValuer) (m_ByValuer = std::move(byValuer); ) void setByRefer(const Accounter& byRefer) ( m_ByRefer = byRefer; ) void setByValuer_overValuer aluerAndNotMover = byVal uerAndNotMover؛ ) باطل setRvaluer (Accounter&& rvaluer) ( m_Rvaluer = std::move(rvaluer); ) );

اگرچه برای اهداف این مقاله ما فقط به دو عملکرد اول علاقه مندیم، من چهار گزینه را برای استفاده از آنها به عنوان کنتراست قرار داده ام.

کلاس Accounter یک کلاس ساده است که تعداد دفعات کپی/انتقال آن را محاسبه می کند. و در کلاس CopyMover توابعی را پیاده سازی کرده ایم که به ما امکان می دهد گزینه های زیر را در نظر بگیریم:

    در حال حرکتاستدلال را تصویب کرد.

    عبور از ارزش، به دنبال آن کپی برداریاستدلال را تصویب کرد.

حالا اگر به هر یک از این توابع یک lvalue ارسال کنیم، مثلاً به این صورت:

حسابدار توسط Refer; حسابدار توسط Valuer; حسابدار توسط ValuerAndNotMover. CopyMover copyMover; copyMover.setByRefer(byRefer); copyMover.setByValuer(byValuer); copyMover.setByValuerAndNotMover(byValuerAndNotMover);

سپس نتایج زیر را دریافت می کنیم:

برنده آشکار PPSC است، زیرا ... فقط یک کپی می دهد، در حالی که PPZ یک کپی و یک حرکت می دهد.

حالا بیایید سعی کنیم مقدار rvalue را پاس کنیم:

CopyMover copyMover; copyMover.setByRefer(Accounter()); copyMover.setByValuer(Accounter()); copyMover.setByValuerAndNotMover(Accounter()); copyMover.setRvaluer(Accounter());

موارد زیر را دریافت می کنیم:

هیچ برنده مشخصی در اینجا وجود ندارد، زیرا ... هر دو PPZ و PPSK هر کدام یک عملیات دارند، اما با توجه به اینکه PPZ از حرکت و PPSK از کپی استفاده می کند، می توانیم پیروزی را به PPZ بدهیم.

اما آزمایش‌های ما به همین جا ختم نمی‌شود؛ بیایید توابع زیر را برای شبیه‌سازی یک فراخوانی غیرمستقیم (با ارسال آرگومان بعدی) اضافه کنیم:

Void setByValuer(Accounter byValuer, CopyMover& copyMover) ( copyMover.setByValuer(std::move(byValuer)); ) void setByRefer(const Accounter& byRefer, CopyMover& copyMover) ( copyMover.setByRefer);

ما از آنها دقیقاً به همان روشی که بدون آنها استفاده می کردیم استفاده خواهیم کرد، بنابراین کد را تکرار نمی کنم (در صورت لزوم به مخزن نگاه کنید). بنابراین برای lvalue نتایج به این صورت خواهد بود:

توجه داشته باشید که PPSC فاصله را با PPZ افزایش می‌دهد و با یک کپی باقی می‌ماند، در حالی که PPZ در حال حاضر 3 عملیات (یک حرکت دیگر) دارد!

حالا مقدار rvalue را پاس می کنیم و نتایج زیر را می گیریم:

اکنون PPZ دارای 2 حرکت است و PPSC هنوز یک نسخه دارد. آیا اکنون می توان PPZ را به عنوان برنده معرفی کرد؟ نه، زیرا اگر یک حرکت حداقل بدتر از یک کپی نباشد، نمی توانیم در مورد 2 حرکت همین را بگوییم. بنابراین در این مثال هیچ برنده ای وجود نخواهد داشت.

آنها ممکن است به من اعتراض کنند: "نویسنده، شما نظر مغرضانه ای دارید و آنچه را که به نفع خود است می کشید. حتی 2 حرکت هم ارزان تر از کپی کردن خواهد بود!» من نمی توانم با این گفته موافق باشم در مجموع، زیرا سرعت حرکت سریعتر از کپی کردن به کلاس خاص بستگی دارد، اما ما در بخش جداگانه ای به جابجایی "ارزان" نگاه خواهیم کرد.

در اینجا ما به یک چیز جالب توجه کردیم: یک تماس غیرمستقیم اضافه کردیم و PPP دقیقاً یک عملیات در "وزن" اضافه کرد. من فکر می کنم برای درک اینکه هرچه تماس های غیرمستقیم بیشتری داشته باشیم، هنگام استفاده از PPZ، نیازی به داشتن مدرک دیپلم از MSTU نیست، در حالی که برای PPSC این شماره بدون تغییر باقی می ماند.

همه چیزهایی که در بالا مورد بحث قرار گرفت بعید بود برای کسی مکاشفه باشد، ما حتی ممکن است آزمایشی انجام نداده باشیم - همه این اعداد باید برای اکثر برنامه نویسان C++ در نگاه اول واضح باشد. درست است، یک نکته هنوز شایسته توضیح است: چرا، در مورد rvalue، PZ یک کپی (یا حرکت دیگری) ندارد، بلکه فقط یک حرکت دارد.

خوب، ما با مشاهده دست اول تعداد کپی ها و حرکت ها به تفاوت انتقال بین PPZ و PPSC نگاهی انداختیم. اگرچه بدیهی است که مزیت PPZ نسبت به PPSC حتی در چنین مثال های ساده ای، به بیان ملایم است. نهبدیهی است که من هنوز، کمی با ادعا، نتیجه‌گیری زیر را انجام می‌دهم: اگر همچنان می‌خواهیم آرگومان تابع را کپی کنیم، منطقی است که آرگومان را بر اساس مقدار به تابع در نظر بگیریم. چرا این نتیجه را گرفتم؟ برای اینکه به آرامی به بخش بعدی بروید.

اگر کپی کنیم...

بنابراین، به ضرب المثل "اگر" می رسیم. اکثر استدلال‌هایی که با آن‌ها مواجه شدیم، اجرای جهانی PPP را به جای PPSC نمی‌خواستند؛ آنها فقط خواستار انجام این کار بودند «در صورتی که استدلال به هر حال کپی شود». وقت آن رسیده است که بفهمیم این استدلال چه اشکالی دارد.

می خواهم با توضیح مختصری در مورد نحوه نوشتن کد شروع کنم. اخیراً، فرآیند کدنویسی من بیشتر و بیشتر شبیه TDD شده است، یعنی. نوشتن هر متد کلاسی با نوشتن تستی شروع می شود که در آن این متد ظاهر می شود. بر این اساس، هنگام شروع به نوشتن یک تست، و ایجاد یک روش پس از نوشتن آزمون، هنوز نمی دانم که آیا استدلال را کپی خواهم کرد یا خیر. البته، همه توابع به این شکل ایجاد نمی شوند؛ اغلب، حتی در فرآیند نوشتن یک تست، دقیقاً می دانید که چه نوع پیاده سازی وجود دارد. اما همیشه این اتفاق نمی افتد!

ممکن است کسی به من اعتراض کند که فرقی نمی‌کند روش در اصل چگونه نوشته شده است، ما می‌توانیم نحوه انتقال استدلال را وقتی تغییر دهیم که روش شکل گرفت و برای ما کاملاً روشن بود که در آنجا چه اتفاقی می‌افتد (یعنی آیا ما کپی داریم یا خیر. نه). من تا حدی با این موافقم - در واقع، شما می توانید این کار را به این طریق انجام دهید، اما این ما را درگیر نوعی بازی عجیب و غریب می کند که در آن ما مجبوریم رابط ها را تغییر دهیم فقط به این دلیل که پیاده سازی تغییر کرده است. که ما را به معضل بعدی می رساند.

به نظر می رسد که ما رابط را بر اساس نحوه پیاده سازی آن تغییر می دهیم (یا حتی برنامه ریزی می کنیم). من خودم را در OOP و سایر محاسبات نظری معماری نرم افزار متخصص نمی دانم، اما چنین اقداماتی به وضوح با قوانین اساسی در تضاد است که پیاده سازی نباید بر رابط تأثیر بگذارد. البته، برخی از جزئیات پیاده‌سازی (خواه ویژگی‌های زبان یا پلتفرم مورد نظر) هنوز به هر طریقی از طریق رابط درز می‌کنند، اما باید سعی کنید تعداد این موارد را کاهش دهید، نه افزایش دهید.

خوب خدا رحمتش کنه این مسیر رو برویم و باز هم بسته به اینکه در پیاده سازی از نظر کپی آرگومان چه کاری انجام می دهیم اینترفیس ها را تغییر دهیم. فرض کنید این روش را نوشتیم:

نامعتبر setName (نام نام) ( m_Name = move(name)؛ )

و تغییرات ما را به مخزن متعهد کردیم. با گذشت زمان، محصول نرم‌افزاری ما قابلیت‌های جدیدی پیدا کرد، چارچوب‌های جدیدی یکپارچه شدند و وظیفه اطلاع‌رسانی به دنیای خارج در مورد تغییرات در کلاس ما مطرح شد. آن ها ما مکانیزم اعلان را به روش خود اضافه می کنیم، اجازه دهید چیزی شبیه به سیگنال های Qt باشد:

نامعتبر setName (Name name) ( m_Name = move(name); emit nameChanged (m_Name)؛ )

آیا این کد مشکلی دارد؟ بخور برای هر تماس با setName یک سیگنال ارسال می‌کنیم، بنابراین سیگنال حتی زمانی ارسال می‌شود معنی m_نام تغییر نکرده است. علاوه بر مشکلات عملکرد، این وضعیت می‌تواند به یک حلقه بی‌نهایت منجر شود، زیرا کدی که اعلان فوق را دریافت می‌کند به نحوی با setName تماس می‌گیرد. برای جلوگیری از همه این مشکلات، چنین روش هایی اغلب شبیه به این هستند:

نامعتبر setName (Name name) ( if(name == m_Name) بازگشت؛ m_Name = move(name); emit nameChanged(m_Name)؛ )

ما از شر مشکلاتی که در بالا توضیح داده شد خلاص شدیم، اما اکنون قانون "اگر به هر حال کپی کنیم ..." ما شکست خورده است - دیگر کپی بی قید و شرط از استدلال وجود ندارد، اکنون فقط در صورت تغییر آن را کپی می کنیم! خب حالا باید چیکار کنیم؟ رابط کاربری را تغییر دهید؟ خوب، اجازه دهید رابط کلاس را به دلیل این اصلاح تغییر دهیم. اگر کلاس ما این متد را از یک رابط انتزاعی به ارث برده باشد چه؟ اونجا هم عوضش کنیم! آیا تغییرات زیادی وجود دارد زیرا اجرا تغییر کرده است؟

باز هم ممکن است به من اعتراض کنند، می گویند نویسنده، چرا سعی می کنید در پول کبریت صرفه جویی کنید در حالی که این شرایط در آنجا حل می شود؟ بله، بیشتر تماس ها نادرست خواهد بود! آیا در این مورد اطمینان وجود دارد؟ جایی که؟ و اگر تصمیم گرفتم در مسابقات صرفه جویی کنم، آیا این واقعیت که ما از PPZ استفاده کردیم دقیقاً نتیجه چنین پس انداز نبود؟ من فقط "خط حزب" را که از کارآمدی حمایت می کند، ادامه می دهم.

سازندگان

اجازه دهید به طور خلاصه به سازندگان بپردازیم، به خصوص که قانون خاصی برای آنها در clang-tidy وجود دارد، که هنوز برای سایر روش ها / عملکردها کار نمی کند. فرض کنید کلاسی مانند این داریم:

کلاس JustClass ( public: JustClass(const string& justString): m_JustString(justString) ( ) private: string m_JustString; );

بدیهی است که پارامتر کپی شده است و clang-tidy به ما می گوید که ایده خوبی است که سازنده را به این شکل بازنویسی کنیم:

JustClass(string justString): m_JustString(move(justString)) ( )

و صادقانه بگویم، بحث کردن در اینجا برای من دشوار است - از این گذشته، ما واقعاً همیشه کپی می کنیم. و اغلب، وقتی چیزی را از یک سازنده عبور می دهیم، آن را کپی می کنیم. اما بیشتر اوقات به این معنی نیست که همیشه. این هم یک مثال دیگر:

Class TimeSpan ( public: TimeSpan(DateTime start, DateTime end) ( if(start > end) throw InvalidTimeSpan(); m_Start = move(start); m_End = move(end); ) private: DateTime m_Start; DateTime m_End; );

در اینجا ما همیشه کپی نمی کنیم، اما فقط زمانی که تاریخ ها به درستی ارائه شده اند. البته در اکثریت قریب به اتفاق موارد اینگونه خواهد بود. ولی نه همیشه.

می توانید مثال دیگری بزنید، اما این بار بدون کد. تصور کنید کلاسی دارید که یک شی بزرگ را می پذیرد. این کلاس برای مدت طولانی وجود داشته است، و اکنون زمان به روز رسانی اجرای آن است. ما متوجه شده ایم که به بیش از نیمی از یک مرکز بزرگ (که در طول سال ها رشد کرده است) و حتی شاید کمتر نیاز نداریم. آیا می‌توانیم با داشتن ارزش عبور کاری در این مورد انجام دهیم؟ نه، ما نمی‌توانیم کاری انجام دهیم، زیرا همچنان یک کپی ایجاد می‌شود. اما اگر از PPSC استفاده می کردیم، به سادگی کاری را که انجام می دهیم تغییر می دهیم داخلطراح. و این نکته کلیدی است: با استفاده از PPSC ما کنترل می کنیم که در اجرای تابع (سازنده) چه اتفاقی و چه زمانی بیفتد، اما اگر از PPZ استفاده کنیم، هر گونه کنترلی بر روی کپی کردن از دست می دهیم.

چه چیزی را می توانید از این بخش حذف کنید؟ این واقعیت که استدلال "اگر ما به هر حال کپی کنیم ..." بسیار بحث برانگیز است، زیرا ما همیشه نمی دانیم چه چیزی را کپی خواهیم کرد، و حتی زمانی که می دانیم، اغلب مطمئن نیستیم که این کار در آینده ادامه پیدا کند.

جابجایی ارزان است

از همان لحظه ای که معناشناسی حرکت ظاهر شد، تأثیر جدی بر نحوه نگارش کدهای C++ مدرن گذاشت و با گذشت زمان این تأثیر فقط تشدید شد: جای تعجب نیست، زیرا حرکت بسیار است. ارزاندر مقایسه با کپی کردن اما آیا اینطور است؟ آیا این درست است که جنبش است همیشهجراحی ارزان؟ این چیزی است که ما سعی خواهیم کرد در این بخش بفهمیم.

شی بزرگ باینری

بیایید با یک مثال بی اهمیت شروع کنیم، فرض کنید کلاس زیر را داریم:

Struct Blob ( std::array داده ها؛ )

معمولی لکه(BDO، انگلیسی BLOB)، که می تواند در موقعیت های مختلف استفاده شود. بیایید ببینیم که عبور از طریق مرجع و ارزش چه هزینه ای برای ما خواهد داشت. BDO ما چیزی شبیه به این استفاده خواهد شد:

فضای خالی

و این توابع را به این صورت صدا خواهیم کرد:

Const Blob blob(); ذخیره سازی؛ storage.setBlobByRef(blob); storage.setBlobByVal(blob);

کد سایر نمونه ها با این یکی، فقط با نام ها و انواع مختلف یکسان خواهد بود، بنابراین برای نمونه های باقی مانده آن را نمی دهم - همه چیز در مخزن است.

قبل از اینکه به اندازه گیری ها بپردازیم، بیایید سعی کنیم نتیجه را پیش بینی کنیم. بنابراین ما یک آرایه 4 کیلوبایتی std:: داریم که می خواهیم در یک شی کلاس Storage ذخیره کنیم. همانطور که قبلا متوجه شدیم، برای PPSC یک کپی خواهیم داشت، در حالی که برای PPZ یک کپی و یک حرکت خواهیم داشت. بر اساس این واقعیت که امکان جابجایی آرایه وجود ندارد، 2 نسخه برای PPZ در مقابل یک نسخه برای PPSC وجود خواهد داشت. آن ها می توان انتظار برتری دو برابری در عملکرد PPSC را داشت.

حالا بیایید نگاهی به نتایج آزمایش بیندازیم:

این و همه آزمایش‌های بعدی با استفاده از MSVS 2017 (15.7.2) و پرچم /O2 روی یک دستگاه اجرا شدند.

تمرین با این فرض مصادف شد - عبور از مقدار 2 برابر گرانتر است، زیرا برای یک آرایه، حرکت کاملاً معادل کپی کردن است.

خط

بیایید به مثال دیگری نگاه کنیم، یک std::string معمولی. چه انتظاری می توانیم داشته باشیم؟ ما می دانیم (در مقاله در مورد این موضوع بحث کردم) که پیاده سازی های مدرن بین دو نوع رشته تمایز قائل می شوند: کوتاه (حدود 16 کاراکتر) و طولانی (آنهایی که طولانی تر از کوتاه هستند). برای موارد کوتاه، یک بافر داخلی استفاده می شود که یک آرایه C معمولی از char است، اما موارد طولانی قبلاً روی پشته قرار می گیرند. ما علاقه ای به خطوط کوتاه نداریم، زیرا ... نتیجه در آنجا مانند BDO خواهد بود، بنابراین بیایید روی صف های طولانی تمرکز کنیم.

بنابراین، با داشتن یک رشته طولانی، بدیهی است که جابجایی آن باید کاملاً ارزان باشد (فقط نشانگر را حرکت دهید)، بنابراین می توانید روی این واقعیت حساب کنید که حرکت رشته به هیچ وجه نباید روی نتایج تأثیر بگذارد و PPZ باید نتیجه بدهد. بدتر از PPSC نیست. بیایید آن را در عمل بررسی کنیم و نتایج زیر را دریافت کنیم:

ما به توضیح این "پدیده" خواهیم پرداخت. پس چه اتفاقی می‌افتد وقتی یک رشته موجود را در یک رشته موجود کپی می‌کنیم؟ بیایید به یک مثال بی اهمیت نگاه کنیم:

رشته اول (64، "C"); رشته دوم (64، "N"); //... دوم = اول;

ما دو رشته 64 کاراکتری داریم، بنابراین بافر داخلی هنگام ایجاد آنها کافی نیست، در نتیجه هر دو رشته روی پشته تخصیص داده می شوند. حالا اول به دوم کپی می کنیم. زیرا اندازه ردیف ما یکسان است، بدیهی است که فضای کافی در قسمت دوم اختصاص داده شده است تا تمام داده های اول را در خود جای دهد، بنابراین دوم = اول. یک memcpy پیش پا افتاده خواهد بود، نه بیشتر. اما اگر به یک مثال کمی اصلاح شده نگاه کنیم:

رشته اول (64، "C"); رشته دوم = اول;

سپس دیگر فراخوانی به operator= وجود نخواهد داشت، بلکه سازنده کپی فراخوانی می شود. زیرا از آنجایی که ما با یک سازنده سروکار داریم، هیچ حافظه موجود در آن وجود ندارد. ابتدا باید انتخاب شود و سپس ابتدا کپی شود. آن ها این تخصیص حافظه و سپس memcpy است. همانطور که من و شما می دانیم، تخصیص حافظه در پشته جهانی معمولاً یک عملیات گران است، بنابراین کپی کردن از مثال دوم گران تر از کپی کردن از نمونه اول خواهد بود. تخصیص حافظه هر پشته گران تر است.

این چه ربطی به موضوع ما دارد؟ مستقیم ترین، زیرا مثال اول دقیقاً آنچه را که با PPSC اتفاق می افتد نشان می دهد، و دومی نشان می دهد که با PPZ چه اتفاقی می افتد: برای PPZ همیشه یک ردیف جدید ایجاد می شود، در حالی که برای PPSC ردیف موجود مجددا استفاده می شود. شما قبلاً تفاوت در زمان اجرا را مشاهده کرده اید، بنابراین چیزی برای اضافه کردن در اینجا وجود ندارد.

در اینجا دوباره با این واقعیت روبرو هستیم که هنگام استفاده از PPP، خارج از زمینه کار می کنیم و بنابراین نمی توانیم از تمام مزایایی که می تواند ارائه دهد استفاده کنیم. و اگر قبلاً از نظر تغییرات نظری آینده استدلال می‌کردیم، در اینجا شاهد یک شکست کاملاً ملموس در بهره‌وری هستیم.

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

چه چیزی را باید از این بخش حذف کنید؟ این واقعیت که حتی اگر جابجایی واقعاً ارزان باشد، به این معنی نیست که جایگزینی کپی با کپی + جابجایی همیشه نتیجه ای قابل مقایسه از نظر عملکرد به همراه خواهد داشت.

نوع پیچیده

در نهایت، بیایید به نوعی نگاه کنیم که از چندین شی تشکیل شده است. بگذارید این کلاس Person باشد که از داده های ذاتی یک شخص تشکیل شده است. معمولاً این نام، نام خانوادگی، کد پستی و غیره شماست. شما می توانید همه اینها را به صورت رشته نمایش دهید و فرض کنید رشته هایی که در فیلدهای کلاس Person قرار می دهید احتمالا کوتاه هستند. اگرچه من معتقدم که در زندگی واقعی، اندازه گیری رشته های کوتاه بسیار مفید خواهد بود، اما همچنان به رشته هایی با اندازه های مختلف نگاه می کنیم تا تصویر کامل تری ارائه دهیم.

من همچنین از Person با 10 فیلد استفاده خواهم کرد، اما برای این کار 10 فیلد را مستقیماً در بدنه کلاس ایجاد نمی کنم. اجرای Person یک ظرف را در اعماق خود پنهان می کند - این باعث می شود تغییر پارامترهای آزمایش راحت تر شود، بدون انحراف از نحوه کار اگر Person یک کلاس واقعی بود. با این حال، پیاده سازی در دسترس است و شما همیشه می توانید کد را بررسی کنید و به من بگویید که آیا کار اشتباهی انجام داده ام.

بنابراین، بیایید برویم: شخصی با 10 فیلد از نوع رشته، که با استفاده از PPSC و PPZ به Storage منتقل می کنیم:

همانطور که می بینید، ما تفاوت بسیار زیادی در عملکرد داریم که نباید بعد از بخش های قبلی برای خوانندگان تعجب آور باشد. من همچنین معتقدم که کلاس Person به اندازه کافی "واقعی" است که چنین نتایجی به عنوان انتزاعی رد نمی شوند.

ضمناً، وقتی داشتم این مقاله را آماده می کردم، یک مثال دیگر آماده کردم: کلاسی که از چندین شیء std::function استفاده می کند. طبق تصور من هم قرار بود برتری در عملکرد PPSC نسبت به PPZ نشان دهد، اما دقیقا برعکس شد! اما من این مثال را در اینجا نمی زنم نه به این دلیل که نتایج را دوست نداشتم، بلکه به این دلیل که وقت نداشتم بفهمم چرا چنین نتایجی به دست آمده است. با این وجود، کدی در مخزن (چاپگرها)، تست ها وجود دارد - همچنین، اگر کسی می خواهد آن را بفهمد، خوشحال می شوم در مورد نتایج تحقیق بشنوم. من قصد دارم بعداً به این مثال بازگردم و اگر کسی قبل از من این نتایج را منتشر نکرد، آنها را در یک مقاله جداگانه بررسی خواهم کرد.

نتایج

بنابراین ما به مزایا و معایب مختلف عبور از ارزش و عبور با ارجاع به یک ثابت نگاه کرده‌ایم. ما به چند نمونه نگاه کردیم و عملکرد هر دو روش را در این مثال ها بررسی کردیم. البته، این مقاله نمی تواند و جامع نیست، اما، به نظر من، حاوی اطلاعات کافی برای تصمیم گیری مستقل و آگاهانه در مورد بهترین روش است. ممکن است کسی اعتراض کند: "چرا از یک روش استفاده کنیم، بیایید از کار شروع کنیم!" در حالی که به طور کلی با این تز موافقم، اما در این شرایط با آن مخالفم. من معتقدم که تنها یک راه برای انتقال استدلال در یک زبان وجود دارد، که به صورت پیش فرض استفاده می شود.

پیش فرض به چه معناست؟ این بدان معناست که وقتی من یک تابع را می نویسم، به این فکر نمی کنم که چگونه باید آرگومان را پاس کنم، فقط از "پیش فرض" استفاده می کنم. زبان C++ زبان نسبتاً پیچیده ای است که بسیاری از مردم از آن اجتناب می کنند. و به نظر من، این پیچیدگی نه چندان به دلیل پیچیدگی ساختارهای زبانی موجود در زبان است (یک برنامه نویس معمولی ممکن است هرگز با آنها برخورد نکند)، بلکه به دلیل این واقعیت است که زبان شما را بسیار به فکر فرو می برد: آیا من آزاد شده ام. افزایش حافظه، آیا استفاده از این تابع در اینجا گران است؟ و غیره.

بسیاری از برنامه نویسان (C، C++ و دیگران) نسبت به C++ که پس از سال 2011 ظاهر شد، بی اعتماد هستند و می ترسند. من انتقادهای زیادی شنیده ام که زبان در حال پیچیده تر شدن است، که اکنون فقط "گوروها" می توانند در آن بنویسند و غیره. شخصاً معتقدم که اینطور نیست - برعکس، کمیته زمان زیادی را به دوستانه تر کردن زبان برای مبتدیان اختصاص می دهد و برنامه نویسان باید کمتر در مورد ویژگی های زبان فکر کنند. از این گذشته، اگر مجبور نباشیم با زبان مبارزه کنیم، وقت داریم به کار فکر کنیم. این ساده‌سازی‌ها شامل اشاره‌گرهای هوشمند، توابع لامبدا و بسیاری موارد دیگر است که در زبان ظاهر می‌شوند. در عین حال منکر این نیستم که الان باید بیشتر مطالعه کنیم، اما درس خواندن چه اشکالی دارد؟ یا آیا هیچ تغییری در سایر زبان‌های رایج که باید یاد بگیرند اتفاق نمی‌افتد؟

علاوه بر این، من شک ندارم که اسنوب هایی وجود خواهند داشت که می توانند در پاسخ بگویند: "نمی خواهی فکر کنی؟ سپس برو با PHP بنویس. من حتی نمی خواهم به چنین افرادی پاسخ دهم. من فقط یک مثال از واقعیت بازی می زنم: در قسمت اول Starcraft، وقتی یک کارگر جدید در یک ساختمان ایجاد می شود تا بتواند استخراج مواد معدنی (یا گاز) را شروع کند، باید به صورت دستی به آنجا فرستاده می شد. علاوه بر این، هر بسته مواد معدنی حدی داشت که با رسیدن به آن، افزایش کارگران بی فایده بود و حتی می توانستند با یکدیگر تداخل کنند و تولید را بدتر کنند. این در Starcraft 2 تغییر کرد: کارگران به طور خودکار شروع به استخراج مواد معدنی (یا گاز) می‌کنند و همچنین نشان می‌دهد که در حال حاضر چند کارگر در حال استخراج هستند و سقف این ذخیره چقدر است. این امر تعامل بازیکن با پایگاه را تا حد زیادی ساده می کند و به او اجازه می دهد تا روی جنبه های مهم بازی تمرکز کند: ساختن پایگاه، جمع آوری نیرو و از بین بردن دشمن. به نظر می رسد که این فقط یک نوآوری عالی است، اما چیزی که در اینترنت شروع شد! مردم (آنها چه کسانی هستند؟) شروع به فریاد زدن کردند که بازی در حال "گند زدن" است و "استارکرافت را کشتند." بدیهی است که چنین پیام هایی فقط می تواند از سوی "حافظان دانش مخفی" و "متخصصان APM بالا" باشد که دوست داشتند در یک باشگاه "نخبگان" حضور داشته باشند.

بنابراین، با بازگشت به موضوع خود، هرچه کمتر به نحوه نوشتن کد فکر کنم، زمان بیشتری برای حل مشکل فوری دارم. فکر کردن به اینکه از کدام روش باید استفاده کنم - PPSC یا PPZ - من را یک ذره به حل مشکل نزدیک نمی کند، بنابراین من به سادگی از فکر کردن به چنین چیزهایی امتناع می ورزم و یک گزینه را انتخاب می کنم: عبور با ارجاع به یک ثابت. چرا؟ زیرا من هیچ مزیتی برای PPP در موارد کلی نمی بینم و موارد خاص باید جداگانه بررسی شوند.

این یک مورد خاص است، فقط با توجه به اینکه در برخی از روش ها PPSC یک گلوگاه است و با تغییر انتقال به PPZ، افزایش قابل توجهی در عملکرد خواهیم داشت، در استفاده از آن تردیدی ندارم. PPZ. اما به طور پیش فرض، من از PPSC هم در توابع معمولی و هم در سازنده ها استفاده خواهم کرد. و در صورت امکان، من این روش خاص را تا جایی که امکان دارد تبلیغ خواهم کرد. چرا؟ زیرا من فکر می کنم رویه ترویج PPP به دلیل این واقعیت است که سهم عمده برنامه نویسان چندان آگاه نیستند (یا اصولاً یا به سادگی هنوز وارد چرخش کارها نشده اند) و آنها به سادگی از توصیه ها پیروی می کنند. بعلاوه، اگر چندین توصیه متناقض وجود داشته باشد، آنها یکی را انتخاب می کنند که ساده تر است، و این منجر به بدبینی در کد می شود، زیرا کسی در جایی چیزی شنیده است. اوه بله، این شخص همچنین می تواند پیوندی به مقاله آبراهامز ارائه دهد تا ثابت کند حق با اوست. و سپس می نشینید، کد را می خوانید و فکر می کنید: آیا این واقعیت است که پارامتر در اینجا با مقدار پاس داده می شود زیرا برنامه نویسی که این را نوشته از جاوا آمده است، فقط مقالات "هوشمند" زیادی را خوانده است یا واقعاً نیاز به یک مشخصات فنی؟

خواندن PPSC بسیار ساده تر است: شخص به وضوح "شکل خوب" C++ را می شناسد و ما به جلو می رویم - نگاه نمی ماند. روش استفاده از PPSC سالهاست که به برنامه نویسان ++C آموزش داده شده است، دلیل کنار گذاشتن آن چیست؟ این من را به نتیجه دیگری می رساند: اگر یک رابط متد از PPP استفاده می کند، باید نظری نیز وجود داشته باشد که چرا اینطور است. در موارد دیگر، PPSC باید اعمال شود. البته، انواع استثنا وجود دارد، اما من آنها را در اینجا ذکر نمی‌کنم صرفاً به این دلیل که به صورت ضمنی هستند: string_view، initializer_list، تکرارکننده‌های مختلف و غیره. اما اینها استثناهایی هستند که لیست آنها بسته به نوع مورد استفاده در پروژه می تواند گسترش یابد. اما ماهیت از C++98 یکسان است: به طور پیش فرض ما همیشه از PPCS استفاده می کنیم.

برای std::string به احتمال زیاد تفاوتی در رشته های کوچک وجود نخواهد داشت، بعداً در مورد آن صحبت خواهیم کرد.

من پیشاپیش از حاشیه نویسی پرمدعا در مورد "جای دادن امتیاز" عذرخواهی می کنم، اما باید به نحوی شما را به مقاله جذب کنیم)) به نوبه خود، سعی می کنم اطمینان حاصل کنم که چکیده همچنان انتظارات شما را برآورده می کند.

به طور خلاصه در مورد چه چیزی صحبت می کنیم

همه قبلاً این را می‌دانند، اما در ابتدا به شما یادآوری می‌کنم که چگونه پارامترهای متد را می‌توان در 1C ارسال کرد. آنها را می توان "با مرجع" یا "بر اساس مقدار" منتقل کرد. در حالت اول، همان مقداری را که در نقطه فراخوانی وجود دارد، به متد ارسال می کنیم و در حالت دوم، یک کپی از آن.

به‌طور پیش‌فرض، در 1C، آرگومان‌ها با مرجع ارسال می‌شوند و تغییرات پارامتری در داخل متد از خارج از متد قابل مشاهده است. در اینجا، درک بیشتر سوال بستگی به این دارد که دقیقاً از کلمه "تغییر پارامتر" چه چیزی را درک می کنید. بنابراین، این به معنای واگذاری مجدد است و نه چیزی بیشتر. علاوه بر این، انتساب می تواند ضمنی باشد، برای مثال، فراخوانی یک متد پلتفرم که چیزی را در پارامتر خروجی برمی گرداند.

اما اگر نمی‌خواهیم پارامتر ما با مرجع ارسال شود، می‌توانیم یک کلمه کلیدی را قبل از پارامتر تعیین کنیم. معنی

رویه ByValue(Value Parameter) Parameter = 2; پارامتر EndProcedure = 1; ByValue (پارامتر)؛ گزارش (پارامتر)؛ // 1 را چاپ خواهد کرد

همه چیز همانطور که وعده داده شده کار می کند - تغییر (یا به جای "جایگزین کردن") مقدار پارامتر مقدار خارج از روش را تغییر نمی دهد.

خب شوخی چیه

لحظات جالب زمانی شروع می‌شوند که ما شروع می‌کنیم نه انواع اولیه (رشته‌ها، اعداد، تاریخ‌ها و غیره) را به‌عنوان پارامتر، بلکه اشیا را ارسال می‌کنیم. اینجاست که مفاهیمی مانند کپی‌های «کم عمق» و «عمیق» از یک شی، و همچنین اشاره‌گرها (نه در زبان C++، بلکه به عنوان دسته‌های انتزاعی) وارد عمل می‌شوند.

هنگامی که یک شی (مثلاً یک جدول ارزش) را با مرجع ارسال می کنیم، خود مقدار اشاره گر (یک دسته خاص) را ارسال می کنیم که شی را در حافظه پلت فرم نگه می دارد. هنگامی که از مقدار عبور می کند، پلت فرم یک کپی از این اشاره گر ایجاد می کند.

به عبارت دیگر، اگر با ارسال یک شی توسط مرجع، در یک متد مقدار Array را به پارامتر اختصاص دهیم، در نقطه فراخوانی یک آرایه دریافت خواهیم کرد. تخصیص مجدد مقدار ارسال شده توسط مرجع از محل تماس قابل مشاهده است.

Procedure ProcessValue(Parameter) Parameter = New Array; جدول EndProcedure = New ValueTable; ProcessValue (جدول)؛ گزارش(ValueType(Table)); // یک آرایه را خروجی می دهد

اگر شیء را با مقدار پاس کنیم، در نقطه فراخوانی، جدول ارزش ما از بین نخواهد رفت.

محتوای شی و حالت

هنگام عبور از مقدار، کل شیء کپی نمی شود، بلکه فقط نشانگر آن کپی می شود. نمونه شیء ثابت می ماند. فرقی نمی کند که چگونه شی را ارسال می کنید، با مرجع یا مقدار - پاک کردن جدول مقدار، خود جدول را پاک می کند. این تمیز کردن در همه جا قابل مشاهده خواهد بود، زیرا ... فقط یک شی وجود داشت و اهمیتی نداشت که دقیقاً چگونه به روش منتقل می شود.

Procedure ProcessValue(Parameter) Parameter.Clear(); جدول EndProcedure = New ValueTable; Table.Add(); ProcessValue (جدول)؛ گزارش(Table.Quantity()); // 0 را خروجی می دهد

هنگام انتقال اشیا به متدها، پلتفرم با اشاره گرها (مشروط، نه آنالوگ مستقیم از C++) عمل می کند. اگر یک شی با مرجع ارسال شود، سلول حافظه ماشین مجازی 1C که شی در آن قرار دارد می تواند توسط یک شی دیگر بازنویسی شود. اگر یک شی با مقدار ارسال شود، نشانگر کپی می شود و رونویسی شی منجر به بازنویسی مکان حافظه با شی اصلی نمی شود.

در عین حال هر تغییری حالتشی (تمیز کردن، افزودن خواص و غیره) خود شیء را تغییر می دهد و اصلاً ربطی به نحوه و مکان انتقال شیء ندارد. وضعیت یک نمونه شی تغییر کرده است؛ ممکن است مجموعه‌ای از «ارجاعات» و «مقادیر» برای آن وجود داشته باشد، اما نمونه همیشه یکسان است. با ارسال یک شی به یک متد، یک کپی از کل شی ایجاد نمی کنیم.

و این همیشه صادق است، به جز ...

تعامل مشتری و سرور

این پلتفرم تماس های سرور را بسیار شفاف اجرا می کند. ما به سادگی یک متد را فراخوانی می‌کنیم، و در زیر هود، پلتفرم تمام پارامترهای متد را سریال‌سازی می‌کند (به یک رشته تبدیل می‌کند)، آنها را به سرور ارسال می‌کند، و سپس پارامترهای خروجی را به مشتری برمی‌گرداند، جایی که آنها از سریال خارج می‌شوند و به‌عنوان زندگی می‌کنند. اگر هرگز به هیچ سروری نرفته بودند.

همانطور که می دانید، همه اشیاء پلتفرم قابل سریال سازی نیستند. اینجاست که محدودیت افزایش می‌یابد: همه اشیا را نمی‌توان از مشتری به متد سرور ارسال کرد. اگر از یک شی غیرقابل سریال عبور کنید، پلتفرم شروع به استفاده از کلمات بد می کند.

  • اعلام صریح از مقاصد برنامه نویس. با نگاه کردن به امضای متد، به وضوح می توانید تشخیص دهید که کدام پارامترها ورودی و کدام ها خروجی هستند. خواندن و نگهداری این کد آسان تر است
  • برای اینکه تغییر در پارامتر "با مرجع" در سرور در نقطه تماس مشتری قابل مشاهده باشد، pخود پلتفرم الزاماً پارامترهای ارسال شده به سرور را از طریق پیوند به کلاینت به منظور اطمینان از رفتار شرح داده شده در ابتدای مقاله برمی گرداند. اگر پارامتر نیازی به بازگرداندن نداشته باشد، ترافیک بیش از حد وجود خواهد داشت. برای بهینه سازی تبادل داده، پارامترهایی که در خروجی به مقادیر آنها نیازی نداریم باید با کلمه Value مشخص شوند.

نکته دوم در اینجا قابل توجه است. برای بهینه‌سازی ترافیک، اگر پارامتر با کلمه Value مشخص شده باشد، پلتفرم مقدار پارامتر را به مشتری باز نمی‌گرداند. این همه عالی است، اما منجر به یک اثر جالب می شود.

همانطور که قبلاً گفتم، هنگامی که یک شی به سرور منتقل می شود، سریال سازی اتفاق می افتد، یعنی. یک کپی "عمیق" از شی انجام می شود. و اگر حرفی هست معنیشیء از سرور به سمت کلاینت باز نخواهد گشت. ما این دو واقعیت را اضافه می کنیم و به موارد زیر می رسیم:

&OnServerProcedureByLink(Parameter) Parameter.Clear(); EndProcedure &OnServerProcedureByValue(Value Parameter) Parameter.Clear(); EndProcedure &OnClient Procedure ByValueClient(Value Parameter) Parameter.Clear(); EndProcedure &OnClient Procedure CheckValue() List1= New ListValues; List1.Add("سلام"); List2 = List1.Copy(); List3 = List1.Copy(); // شی به طور کامل کپی می شود، // به سرور منتقل می شود، سپس برگردانده می شود. // پاک کردن لیست در نقطه تماس ByRef(List1) قابل مشاهده است. // شی به طور کامل کپی می شود، // به سرور منتقل می شود. برنمیگرده // پاک کردن لیست در نقطه فراخوانی ByValue(List2) قابل مشاهده نیست. // فقط اشاره گر شی کپی می شود // پاک کردن لیست در نقطه فراخوانی ByValueClient(List3) قابل مشاهده است. گزارش(List1.Quantity()); گزارش(List2.Quantity()); گزارش(List3.Quantity()); پایان رویه

خلاصه

به طور خلاصه می توان آن را به صورت زیر خلاصه کرد:

  • عبور از طریق مرجع به شما امکان می دهد یک شی را با یک شی کاملاً متفاوت "بازنویسی کنید".
  • عبور از مقدار به شما اجازه نمی دهد که شی را "بازنویسی کنید"، اما تغییرات در وضعیت داخلی شی قابل مشاهده خواهد بود، زیرا ما با همان نمونه شی کار می کنیم
  • هنگام برقراری تماس سرور، کار با نمونه های مختلف شی انجام می شود، زیرا یک کپی عمیق انجام شد. کلمه کلیدی معنیاز کپی شدن نمونه سرور به نمونه مشتری جلوگیری می کند و تغییر وضعیت داخلی یک شی در سرور منجر به تغییر مشابهی در مشتری نمی شود.

امیدوارم که این فهرست ساده قوانین حل و فصل اختلافات با همکاران در مورد عبور پارامترهای "بر اساس مقدار" و "با مرجع" را برای شما آسان تر کند.