ผ่านไปค่า. การส่งผ่านพารามิเตอร์โดยการอ้างอิงและตามค่า การตั้งค่าเริ่มต้น

ดังนั้น ให้ Factorial(n) เป็นฟังก์ชันสำหรับคำนวณแฟกทอเรียลของตัวเลข n จากนั้นเมื่อเรา "รู้" แฟคทอเรียล 1 คือ 1 เราจึงสามารถสร้างห่วงโซ่ดังต่อไปนี้:

แฟกทอเรียล(4)=แฟคทอเรียล(3)*4

แฟกทอเรียล(3)=แฟคทอเรียล(2)*3

แฟกทอเรียล(2)=แฟคทอเรียล(1)*2

แต่ถ้าเราไม่มีเงื่อนไขเทอร์มินัลว่าเมื่อ n=1 ฟังก์ชันแฟกทอเรียลควรคืนค่า 1 ดังนั้นห่วงโซ่ทางทฤษฎีดังกล่าวจะไม่มีวันสิ้นสุด และนี่อาจเป็นข้อผิดพลาด Call Stack Overflow - call stack overflow เพื่อทำความเข้าใจว่า Call Stack คืออะไร และมันล้นได้อย่างไร มาดูการใช้งานฟังก์ชันแบบเรียกซ้ำกัน:

ฟังก์ชันแฟกทอเรียล (n: จำนวนเต็ม): LongInt;

ถ้า n=1 แล้ว

แฟกทอเรียล:=แฟกทอเรียล(n-1)*n;

จบ;

ดังที่เราเห็น เพื่อให้ chain ทำงานได้อย่างถูกต้อง ก่อนที่ฟังก์ชันถัดไปจะเรียกใช้ตัวเอง จำเป็นต้องบันทึกตัวแปรภายในเครื่องทั้งหมดไว้ที่ใดที่หนึ่ง เพื่อว่าเมื่อ chain กลับด้าน ผลลัพธ์ก็จะถูกต้อง (ค่าที่คำนวณได้ของ แฟกทอเรียลของ n-1 คูณด้วย n ) ในกรณีของเรา ทุกครั้งที่เรียกฟังก์ชันแฟกทอเรียลจากตัวมันเอง จะต้องบันทึกค่าทั้งหมดของตัวแปร n พื้นที่ที่เก็บตัวแปรภายในของฟังก์ชันเมื่อเรียกตัวเองซ้ำๆ เรียกว่า call stack แน่นอนว่าสแต็กนี้ไม่สิ้นสุดและสามารถหมดลงได้หากมีการสร้างการเรียกซ้ำไม่ถูกต้อง ความละเอียดของการวนซ้ำตัวอย่างของเรารับประกันได้ด้วยข้อเท็จจริงที่ว่าเมื่อ n=1 การเรียกฟังก์ชันหยุดลง

การส่งผ่านพารามิเตอร์ตามค่าและโดยการอ้างอิง

จนถึงขณะนี้เราไม่สามารถเปลี่ยนค่าในรูทีนย่อยได้ พารามิเตอร์จริง(เช่นพารามิเตอร์ที่ระบุไว้เมื่อเรียกใช้รูทีนย่อย) และในบางงานที่นำไปใช้จะสะดวก จำขั้นตอน Val ซึ่งเปลี่ยนค่าของพารามิเตอร์จริงสองตัวพร้อมกัน: ตัวแรกคือพารามิเตอร์ที่จะเขียนค่าที่แปลงแล้วของตัวแปรสตริงและตัวที่สองคือพารามิเตอร์ Code โดยที่จำนวนของค่าที่ผิดพลาด อักขระจะถูกวางไว้ในกรณีที่เกิดความล้มเหลวระหว่างการแปลงประเภท เหล่านั้น. ยังคงมีกลไกที่รูทีนย่อยสามารถเปลี่ยนพารามิเตอร์จริงได้ สิ่งนี้เป็นไปได้ด้วยวิธีการส่งผ่านพารามิเตอร์ที่หลากหลาย ลองมาดูวิธีการเหล่านี้ให้ละเอียดยิ่งขึ้น

การเขียนโปรแกรมในภาษาปาสคาล

การส่งผ่านพารามิเตอร์ตามค่า

โดยพื้นฐานแล้ว นี่คือวิธีที่เราส่งผ่านพารามิเตอร์ทั้งหมดไปยังกิจวัตรของเรา กลไกมีดังนี้: เมื่อมีการระบุพารามิเตอร์จริง ค่าของพารามิเตอร์จะถูกคัดลอกไปยังพื้นที่หน่วยความจำซึ่งมีรูทีนย่อยอยู่ จากนั้นหลังจากฟังก์ชันหรือโพรซีเดอร์ทำงานเสร็จสิ้น พื้นที่นี้จะถูกล้าง พูดโดยคร่าวๆ ในขณะที่รูทีนย่อยกำลังทำงานอยู่ จะมีสำเนาของพารามิเตอร์อยู่สองชุด: ชุดหนึ่งอยู่ในขอบเขตของการเรียกโปรแกรม และชุดที่สองอยู่ในขอบเขตของฟังก์ชัน

ด้วยวิธีการส่งพารามิเตอร์นี้ จะใช้เวลามากขึ้นในการเรียกรูทีนย่อย เนื่องจากนอกเหนือจากการโทรแล้ว จำเป็นต้องคัดลอกค่าทั้งหมดของพารามิเตอร์จริงทั้งหมด หากข้อมูลจำนวนมากถูกส่งไปยังรูทีนย่อย (เช่นอาร์เรย์ที่มีองค์ประกอบจำนวนมาก) เวลาที่ต้องใช้ในการคัดลอกข้อมูลไปยังพื้นที่ท้องถิ่นอาจมีนัยสำคัญและจะต้องนำมาพิจารณาเมื่อพัฒนาโปรแกรมและ พบปัญหาคอขวดในการปฏิบัติงาน

ด้วยวิธีการถ่ายโอนนี้ พารามิเตอร์จริงไม่สามารถเปลี่ยนแปลงได้โดยรูทีนย่อย เนื่องจากการเปลี่ยนแปลงจะมีผลเฉพาะพื้นที่โลคัลที่แยกออกไป ซึ่งจะถูกปล่อยออกมาหลังจากฟังก์ชันหรือโพรซีเดอร์เสร็จสมบูรณ์

การส่งผ่านพารามิเตอร์โดยการอ้างอิง

ด้วยวิธีนี้ ค่าของพารามิเตอร์จริงจะไม่ถูกคัดลอกไปยังรูทีนย่อย แต่จะส่งที่อยู่ในหน่วยความจำ (ลิงก์ไปยังตัวแปร) ที่ซึ่งพารามิเตอร์เหล่านั้นอยู่ ในกรณีนี้รูทีนย่อยกำลังเปลี่ยนค่าที่ไม่อยู่ในขอบเขตท้องถิ่นอยู่แล้ว ดังนั้นการเปลี่ยนแปลงทั้งหมดจะปรากฏแก่โปรแกรมที่เรียกใช้

เพื่อระบุว่าอาร์กิวเมนต์ต้องถูกส่งผ่านโดยการอ้างอิง คำหลัก var จะถูกเพิ่มก่อนการประกาศ:

ขั้นตอน getTwoRandom(var n1, n2:Integer; range: Integer);

n1:=สุ่ม(ช่วง);

n2:=สุ่ม(ช่วง); จบ ;

var rand1, rand2: จำนวนเต็ม;

เริ่ม getTwoสุ่ม(แรนด์1,แรนด์2,10); WriteLn(rand1); WriteLn(rand2);

จบ.

ในตัวอย่างนี้ การอ้างอิงถึงตัวแปรสองตัวจะถูกส่งผ่านไปยังขั้นตอน getTwoRandom เป็นพารามิเตอร์จริง: rand1 และ rand2 พารามิเตอร์จริงตัวที่สาม (10) ถูกส่งผ่านตามค่า ขั้นตอนการเขียนโดยใช้พารามิเตอร์ที่เป็นทางการ

วิธีการเขียนโปรแกรมโดยใช้สตริง

วัตถุประสงค์ของงานห้องปฏิบัติการ : เรียนรู้วิธีในภาษา C# กฎสำหรับการทำงานกับข้อมูลอักขระและส่วนประกอบกล่องรายการ เขียนโปรแกรมเพื่อทำงานกับสตริง

วิธีการ

เมธอดคือองค์ประกอบของคลาสที่มีโค้ดโปรแกรม วิธีการนี้มีโครงสร้างดังต่อไปนี้:

[แอตทริบิวต์] [ตัวระบุ] ชื่อประเภท ([พารามิเตอร์])

เนื้อความวิธีการ;

คุณลักษณะเป็นคำสั่งพิเศษสำหรับคอมไพเลอร์เกี่ยวกับคุณสมบัติของวิธีการ คุณสมบัติไม่ค่อยได้ใช้

ตัวระบุคือคีย์เวิร์ดที่ให้บริการตามวัตถุประสงค์ที่แตกต่างกัน เช่น:

· การกำหนดความพร้อมใช้งานของวิธีการสำหรับคลาสอื่น:

โอ ส่วนตัว– วิธีการนี้จะใช้ได้เฉพาะในคลาสนี้เท่านั้น

โอ มีการป้องกัน– วิธีการนี้จะใช้ได้กับชั้นเรียนย่อยด้วย

โอ สาธารณะ– วิธีการนี้จะใช้ได้กับคลาสอื่นที่สามารถเข้าถึงคลาสนี้ได้

บ่งชี้ความพร้อมใช้งานของวิธีการโดยไม่ต้องสร้างชั้นเรียน

· ประเภทการตั้งค่า

ประเภทจะกำหนดผลลัพธ์ที่เมธอดส่งคืน: อาจเป็นประเภทใดก็ได้ที่มีอยู่ใน C# รวมถึงคีย์เวิร์ด void หากไม่ต้องการผลลัพธ์

ชื่อวิธีการคือตัวระบุที่จะใช้เรียกวิธีการ ข้อกำหนดเดียวกันนี้ใช้กับตัวระบุสำหรับชื่อตัวแปร โดยสามารถประกอบด้วยตัวอักษร ตัวเลข และขีดล่าง แต่ไม่สามารถขึ้นต้นด้วยตัวเลขได้

พารามิเตอร์คือรายการตัวแปรที่สามารถส่งผ่านไปยังเมธอดได้เมื่อเรียกใช้ แต่ละพารามิเตอร์ประกอบด้วยประเภทและชื่อตัวแปร พารามิเตอร์จะถูกคั่นด้วยเครื่องหมายจุลภาค

เนื้อความของเมธอดเป็นโค้ดโปรแกรมปกติ ยกเว้นว่า ไม่สามารถมีคำจำกัดความของเมธอด คลาส เนมสเปซ ฯลฯ ได้ หากเมธอดต้องส่งคืนผลลัพธ์บางอย่าง คีย์เวิร์ด return จะต้องแสดงต่อท้ายด้วยค่าที่ส่งคืน ความหมาย . หากไม่จำเป็นต้องส่งคืนผลลัพธ์ ก็ไม่จำเป็นต้องใช้คีย์เวิร์ด return แม้ว่าจะได้รับอนุญาตก็ตาม

ตัวอย่างของวิธีการที่ประเมินการแสดงออก:

สาธารณะ double Calc (ดับเบิล a, double b, double c)

กลับ Math.Sin(a) * Math.Cos(b);

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

กลับ k * Math.Exp(c / k);

วิธีการโอเวอร์โหลด

ภาษา C# ช่วยให้คุณสร้างวิธีการได้หลายวิธีด้วยชื่อเดียวกันแต่มีพารามิเตอร์ต่างกัน คอมไพเลอร์จะเลือกวิธีการที่เหมาะสมที่สุดโดยอัตโนมัติเมื่อสร้างโปรแกรม ตัวอย่างเช่น คุณสามารถเขียนสองวิธีแยกกันเพื่อเพิ่มจำนวนให้เป็นกำลัง: อัลกอริทึมหนึ่งใช้สำหรับจำนวนเต็ม และอีกวิธีหนึ่งใช้สำหรับจำนวนจริง:

///

/// คำนวณ X ยกกำลัง Y สำหรับจำนวนเต็ม

///

int Pow ส่วนตัว (int X, int Y)

///

/// คำนวณ X ยกกำลัง Y สำหรับจำนวนจริง

///

Pow คู่ส่วนตัว (double X, double Y)

กลับ Math.Exp(Y * Math.Log(Math.Abs(X)));

อย่างอื่นถ้า (Y == 0)

รหัสนี้ถูกเรียกในลักษณะเดียวกันความแตกต่างเพียงอย่างเดียวคือในพารามิเตอร์ - ในกรณีแรกคอมไพเลอร์จะเรียกวิธี Pow ด้วยพารามิเตอร์จำนวนเต็มและในกรณีที่สอง - ด้วยพารามิเตอร์จริง:

การตั้งค่าเริ่มต้น

ภาษา C# เริ่มต้นด้วยเวอร์ชัน 4.0 (Visual Studio 2010) ช่วยให้คุณสามารถตั้งค่าเริ่มต้นสำหรับพารามิเตอร์บางตัวได้ ดังนั้นเมื่อเรียกใช้เมธอด คุณสามารถละเว้นพารามิเตอร์บางตัวได้ ในการดำเนินการนี้ เมื่อใช้วิธีการนี้ ควรกำหนดค่าพารามิเตอร์ที่จำเป็นในรายการพารามิเตอร์โดยตรง:

โมฆะส่วนตัว GetData (หมายเลข int, int ตัวเลือก = 5 )

Console.WriteLine("หมายเลข: (0)", หมายเลข);

Console.WriteLine("ทางเลือก: (0)", ทางเลือก);

ในกรณีนี้ คุณสามารถเรียกวิธีการดังต่อไปนี้:

รับข้อมูล(10, 20);

ในกรณีแรก พารามิเตอร์ทางเลือกจะเท่ากับ 20 เนื่องจากมีการระบุไว้อย่างชัดเจน และในกรณีที่สอง จะเท่ากับ 5 เนื่องจาก ไม่ได้ระบุอย่างชัดเจนและคอมไพเลอร์ใช้ค่าเริ่มต้น

พารามิเตอร์เริ่มต้นสามารถตั้งค่าได้ทางด้านขวาของรายการพารามิเตอร์เท่านั้น ตัวอย่างเช่น ลายเซ็นวิธีการดังกล่าวจะไม่ได้รับการยอมรับโดยคอมไพเลอร์:

GetData เป็นโมฆะส่วนตัว (int Optional = 5 , หมายเลข int)

เมื่อพารามิเตอร์ถูกส่งผ่านไปยังวิธีการปกติ (โดยไม่มีคีย์เวิร์ดอ้างอิงและนอกเพิ่มเติม) การเปลี่ยนแปลงใดๆ ในพารามิเตอร์ภายในวิธีการจะไม่ส่งผลกระทบต่อค่าในโปรแกรมหลัก สมมติว่าเรามีวิธีการดังต่อไปนี้:

Calc เป็นโมฆะส่วนตัว (หมายเลข int)

จะเห็นได้ว่าภายในเมธอด ตัวแปร Number ซึ่งถูกส่งผ่านเป็นพารามิเตอร์มีการเปลี่ยนแปลง ลองเรียกเมธอดนี้:

คอนโซล WriteLine (n);

หมายเลข 1 จะปรากฏบนหน้าจอ กล่าวคือ แม้ว่าตัวแปรในวิธี Calc จะมีการเปลี่ยนแปลง แต่ค่าของตัวแปรในโปรแกรมหลักก็ไม่เปลี่ยนแปลง นี่เป็นเพราะว่าเมื่อมีการเรียกใช้เมธอด a สำเนาตัวแปรที่ส่งผ่านคือตัวแปรนี้ที่วิธีการเปลี่ยนแปลง เมื่อวิธีการสิ้นสุดลง มูลค่าของสำเนาจะหายไป วิธีการส่งพารามิเตอร์นี้เรียกว่า ผ่านไปด้วยคุณค่า.

สำหรับวิธีการเปลี่ยนตัวแปรที่ส่งผ่านไปยังตัวแปรนั้น จะต้องส่งผ่านด้วยคีย์เวิร์ด ref - จะต้องทั้งในลายเซ็นของวิธีการและเมื่อถูกเรียก:

Calc เป็นโมฆะส่วนตัว (หมายเลขอ้างอิง int)

คอนโซล WriteLine (n);

ในกรณีนี้หมายเลข 10 จะปรากฏบนหน้าจอ: การเปลี่ยนแปลงค่าในวิธีการก็ส่งผลต่อโปรแกรมหลักด้วย การโอนวิธีนี้เรียกว่า ผ่านการอ้างอิง, เช่น. ไม่ใช่สำเนาที่ถูกส่งอีกต่อไป แต่เป็นการอ้างอิงถึงตัวแปรจริงในหน่วยความจำ

หากวิธีการใช้ตัวแปรโดยการอ้างอิงเท่านั้นเพื่อส่งคืนค่าและไม่สนใจว่ามีอะไรอยู่ในตัวแปรเหล่านั้นตั้งแต่แรก คุณจะไม่สามารถเริ่มต้นตัวแปรดังกล่าวได้ แต่ส่งผ่านตัวแปรเหล่านั้นด้วยคีย์เวิร์ด out คอมไพเลอร์เข้าใจว่าค่าเริ่มต้นของตัวแปรนั้นไม่สำคัญและไม่บ่นเกี่ยวกับการขาดการกำหนดค่าเริ่มต้น:

การคำนวณโมฆะส่วนตัว (ออกหมายเลข int)

อินท์เอ็น; // เราไม่ได้มอบหมายอะไร!

ชนิดข้อมูลสตริง

ภาษา C# ใช้ประเภทสตริงเพื่อจัดเก็บสตริง เพื่อที่จะประกาศ (และตามกฎแล้ว ให้เริ่มต้นทันที) ตัวแปรสตริง คุณสามารถเขียนโค้ดต่อไปนี้:

สตริง a = "ข้อความ";

สตริง b = "สตริง";

คุณสามารถดำเนินการเพิ่มเติมในบรรทัดได้ - ในกรณีนี้ ข้อความของบรรทัดหนึ่งจะถูกเพิ่มลงในข้อความของอีกบรรทัด:

สตริง c = a + " " + b; // ผลลัพธ์: ข้อความสตริง

จริงๆ แล้วประเภทสตริงเป็นนามแฝงสำหรับคลาส String ซึ่งช่วยให้คุณสามารถดำเนินการกับสตริงที่ซับซ้อนมากขึ้นได้ ตัวอย่างเช่น วิธีการ IndexOf สามารถค้นหาสตริงย่อยในสตริง และวิธีการ Substring ส่งกลับส่วนของสตริงที่มีความยาวที่ระบุ โดยเริ่มต้นที่ตำแหน่งที่ระบุ:

สตริง a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

ดัชนี int = a.IndexOf("OP"); // ผลลัพธ์: 14 (นับจาก 0)

สตริง b = a.สตริงย่อย (3, 5); // ผลลัพธ์: DEFGH

หากคุณต้องการเพิ่มอักขระพิเศษลงในสตริง คุณสามารถทำได้โดยใช้ลำดับการหลีกเลี่ยงที่เริ่มต้นด้วยเครื่องหมายแบ็กสแลช:

ส่วนประกอบกล่องรายการ

ส่วนประกอบ กล่องรายการคือรายการที่มีการเลือกองค์ประกอบโดยใช้แป้นพิมพ์หรือเมาส์ รายการองค์ประกอบจะถูกระบุโดยคุณสมบัติ รายการ. รายการเป็นองค์ประกอบที่มีคุณสมบัติและวิธีการของตัวเอง วิธีการ เพิ่ม, ลบที่และ แทรกใช้เพื่อเพิ่ม ลบ และแทรกองค์ประกอบ

วัตถุ รายการเก็บวัตถุในรายการ วัตถุสามารถเป็นคลาสใดก็ได้ - ข้อมูลคลาสจะถูกแปลงเพื่อแสดงเป็นการแทนสตริงโดยวิธี ToString ในกรณีของเรา สตริงจะทำหน้าที่เป็นวัตถุ อย่างไรก็ตาม เนื่องจากอ็อบเจ็กต์ Items เก็บอ็อบเจ็กต์ที่ส่งเพื่อพิมพ์อ็อบเจ็กต์ ก่อนที่จะใช้งาน คุณจะต้องส่งอ็อบเจ็กต์เหล่านั้นกลับไปเป็นประเภทดั้งเดิม ในสตริงเคสของเรา:

สตริง = (สตริง) listBox1.Items;

ในการกำหนดจำนวนขององค์ประกอบที่เลือก ให้ใช้คุณสมบัติ ดัชนีที่เลือก.

เมื่อฉันเริ่มเขียนโปรแกรมด้วย C++ และศึกษาหนังสือและบทความอย่างเข้มข้น ฉันก็พบคำแนะนำเดียวกันนี้เสมอ: หากเราต้องส่งอ็อบเจ็กต์บางอย่างไปยังฟังก์ชันที่ไม่ควรเปลี่ยนแปลงในฟังก์ชัน ก็ควรส่งผ่านเสมอ โดยอ้างอิงถึงค่าคงที่(PPSK) ยกเว้นกรณีที่เราต้องผ่านประเภทดั้งเดิมหรือโครงสร้างที่มีขนาดใกล้เคียงกัน เพราะ เป็นเวลากว่า 10 ปีของการเขียนโปรแกรมด้วย C++ ฉันเจอคำแนะนำนี้บ่อยมาก (และฉันเองก็เคยให้มาแล้วมากกว่าหนึ่งครั้ง) คำแนะนำนี้ถูก "ซึมซับ" เข้าสู่ตัวฉันมานานแล้ว - ฉันจะส่งผ่านข้อโต้แย้งทั้งหมดโดยอัตโนมัติโดยอ้างอิงกับค่าคงที่ . แต่เวลาผ่านไปและ 7 ปีผ่านไปแล้วนับตั้งแต่ที่เรามี C++11 พร้อมซีแมนทิกส์การเคลื่อนไหว ซึ่งเกี่ยวข้องกับการที่ฉันได้ยินเสียงตั้งคำถามเกี่ยวกับหลักคำสอนที่ดีมากขึ้นเรื่อยๆ หลายคนเริ่มโต้แย้งว่าการส่งผ่านโดยการอ้างอิงถึงค่าคงที่เป็นเรื่องของอดีตและตอนนี้ก็จำเป็น ผ่านไปด้วยคุณค่า(พีพีแซด). สิ่งที่อยู่เบื้องหลังการสนทนาเหล่านี้ รวมถึงข้อสรุปที่เราสามารถได้จากทั้งหมดนี้ ฉันต้องการพูดคุยในบทความนี้

หนังสือภูมิปัญญา

เพื่อทำความเข้าใจว่าเราควรปฏิบัติตามกฎเกณฑ์ใด ฉันแนะนำให้หันไปอ่านหนังสือ หนังสือเป็นแหล่งข้อมูลที่ดีเยี่ยมซึ่งเราไม่จำเป็นต้องยอมรับ แต่คุ้มค่าที่จะรับฟังอย่างแน่นอน และเราจะเริ่มต้นด้วยประวัติศาสตร์ด้วยต้นกำเนิด ฉันจะไม่รู้ว่าใครเป็นผู้ขอโทษคนแรกของ PPSC ฉันจะยกตัวอย่างหนังสือที่มีอิทธิพลต่อฉันมากที่สุดในเรื่องการใช้ PPSC เป็นตัวอย่าง

เมเยอร์ส

โอเค เรามีคลาสที่พารามิเตอร์ทั้งหมดถูกส่งผ่านโดยการอ้างอิง มีปัญหาใดๆ กับคลาสนี้หรือไม่? น่าเสียดายที่มี และปัญหานี้อยู่เพียงผิวเผิน เรามีเอนทิตีการทำงาน 2 รายการในคลาสของเรา: อันแรกรับค่าในขั้นตอนการสร้างออบเจ็กต์ และอันที่สองให้คุณเปลี่ยนค่าที่ตั้งไว้ก่อนหน้านี้ เรามีสองเอนทิตี แต่มีสี่ฟังก์ชัน ทีนี้ลองจินตนาการว่าเราไม่สามารถมีเอนทิตีที่คล้ายกัน 2 ตัวได้ แต่มี 3, 5, 6 แล้วไง? จากนั้นเราจะเผชิญกับการบวมของโค้ดอย่างรุนแรง ดังนั้น เพื่อไม่ให้สร้างฟังก์ชันจำนวนมาก จึงมีข้อเสนอให้ละทิ้งลิงก์ในพารามิเตอร์ทั้งหมด:

แม่แบบ ผู้ถือคลาส ( สาธารณะ: ชัดเจน Holder(T value): m_Value(move(value)) ( ) void setValue(T value) ( ​​​​m_Value = move(value); ) const T& value() const noException ( return m_Value; ) ส่วนตัว: T m_Value; );

ข้อดีประการแรกที่ดึงดูดสายตาคุณทันทีคือมีโค้ดน้อยลงอย่างเห็นได้ชัด มีน้อยกว่าในเวอร์ชันแรกด้วยซ้ำเนื่องจากการลบ const และ & (แม้ว่าจะเพิ่ม move ก็ตาม) แต่เราถูกสอนมาโดยตลอดว่าการส่งผ่านโดยการอ้างอิงมีประสิทธิผลมากกว่าการส่งผ่านตามคุณค่า! เมื่อก่อน C++11 ก็เป็นแบบนี้ และยังคงเป็นเช่นนี้ แต่ตอนนี้ ถ้าเราดูโค้ดนี้ เราจะเห็นว่าไม่มีการคัดลอกที่นี่มากไปกว่าเวอร์ชันแรก โดยมีเงื่อนไขว่า T มีตัวสร้างการเคลื่อนไหว. เหล่านั้น. PPSC เองก็เคยเป็นและจะเร็วกว่า PPZ แต่โค้ดใช้การอ้างอิงที่ส่งผ่าน และบ่อยครั้งที่อาร์กิวเมนต์นี้ถูกคัดลอก

อย่างไรก็ตาม นี่ไม่ใช่เรื่องราวทั้งหมด ต่างจากตัวเลือกแรกที่เรามีเพียงการคัดลอก ที่นี่เรายังเพิ่มการเคลื่อนไหวอีกด้วย แต่การย้ายเป็นการดำเนินการที่ประหยัดใช่ไหม? ในหัวข้อนี้ หนังสือ Mayers ที่เรากำลังพิจารณาก็มีบทหนึ่ง (“รายการ 29”) ซึ่งมีชื่อว่า “สมมติว่าไม่มีการดำเนินการเคลื่อนย้าย ไม่ถูก และไม่ได้ใช้” แนวคิดหลักควรชัดเจนจากชื่อเรื่อง แต่ถ้าคุณต้องการรายละเอียด อย่าลืมอ่าน ฉันจะไม่พูดถึงมันอีกต่อไป

การดำเนินการวิเคราะห์เปรียบเทียบเต็มรูปแบบของวิธีแรกและวิธีสุดท้ายจะเหมาะสมที่นี่ แต่ฉันไม่ต้องการเบี่ยงเบนไปจากหนังสือ ดังนั้นเราจะเลื่อนการวิเคราะห์ไปยังส่วนอื่น ๆ และที่นี่เราจะพิจารณาข้อโต้แย้งของ Scott ต่อไป ดังนั้น นอกเหนือจากข้อเท็จจริงที่ว่าตัวเลือกที่สามนั้นสั้นกว่าตัวเลือกที่สองอย่างเห็นได้ชัด แล้ว Scott มองว่าอะไรเป็นข้อได้เปรียบของ PPZ เหนือ PPSC ในโค้ดสมัยใหม่

เขาเห็นความจริงที่ว่าในกรณีที่ส่งค่า r คือ บางสายเรียกเช่นนี้: Holder holder(string("me"); ตัวเลือกที่มี PPSC จะให้การคัดลอกแก่เรา และตัวเลือกที่มี PPZ จะทำให้เรามีการเคลื่อนไหว ในทางกลับกัน หากการโอนเป็นดังนี้: Holder holder(someLvalue); จากนั้น PPZ จะสูญเสียอย่างแน่นอนเนื่องจากจะทำทั้งการคัดลอกและการย้ายในขณะที่ในเวอร์ชันที่มี PPSC จะมีการคัดลอกเพียงครั้งเดียว เหล่านั้น. ปรากฎว่า PPZ ถ้าเราพิจารณาถึงประสิทธิภาพล้วนๆ เป็นการประนีประนอมระหว่างจำนวนโค้ดและการสนับสนุน "เต็ม" (ผ่าน && ) สำหรับซีแมนทิกส์การเคลื่อนไหว

นั่นเป็นสาเหตุที่สก็อตต์ใช้คำแนะนำของเขาอย่างระมัดระวังและส่งเสริมอย่างระมัดระวัง สำหรับฉันดูเหมือนว่าเขาจะหยิบยกเรื่องนี้ขึ้นมาอย่างไม่เต็มใจราวกับตกอยู่ภายใต้แรงกดดัน: เขาอดไม่ได้ที่จะอภิปรายการหัวข้อนี้ในหนังสือเพราะ... มีการพูดคุยกันอย่างกว้างขวาง และสก็อตต์ก็เป็นนักสะสมประสบการณ์ร่วมกันมาโดยตลอด นอกจากนี้ เขายังให้ข้อโต้แย้งน้อยมากในการป้องกัน PPZ แต่เขาให้ข้อโต้แย้งจำนวนมากที่เรียกว่า "เทคนิค" นี้เป็นคำถาม เราจะดูข้อโต้แย้งของเขาในหัวข้อต่อๆ ไป แต่ที่นี่เราจะทบทวนข้อโต้แย้งที่สก็อตต์ทำเพื่อปกป้องพรรคพลังประชาชนในช่วงสั้น ๆ (เพิ่มทางจิต “ถ้าวัตถุนั้นรองรับการเคลื่อนไหวและราคาถูก”): ช่วยให้คุณหลีกเลี่ยงการคัดลอกเมื่อส่งนิพจน์ rvalue เป็นอาร์กิวเมนต์ของฟังก์ชัน แต่พอหนังสือของเมเยอร์สทรมานมากพอแล้ว เรามาต่อกันที่หนังสือเล่มอื่นกันดีกว่า

อย่างไรก็ตาม หากใครได้อ่านหนังสือแล้วและรู้สึกประหลาดใจที่ฉันไม่ได้รวมตัวเลือกไว้ที่นี่กับสิ่งที่ Mayers เรียกว่าการอ้างอิงสากล ซึ่งปัจจุบันเรียกว่าการอ้างอิงการส่งต่อ ก็สามารถอธิบายได้อย่างง่ายดาย ฉันกำลังพิจารณาเฉพาะ PPZ และ PPSC เท่านั้น เพราะ... ฉันคิดว่ามันเป็นรูปแบบที่ไม่ดีที่จะแนะนำฟังก์ชันเทมเพลตสำหรับวิธีการที่ไม่ใช่เทมเพลตเพียงเพื่อรองรับการส่งผ่านโดยการอ้างอิงทั้งสองประเภท (rvalue/lvalue) ไม่ต้องพูดถึงความจริงที่ว่าโค้ดนั้นแตกต่างออกไป (ไม่มีความสม่ำเสมออีกต่อไป) และนำมาซึ่งปัญหาอื่น ๆ ด้วย

Josattis และคณะ

หนังสือเล่มสุดท้ายที่เราจะดูคือ “เทมเพลต C++” ซึ่งเป็นหนังสือเล่มล่าสุดในบรรดาหนังสือทั้งหมดที่กล่าวถึงในบทความนี้ จัดพิมพ์เมื่อปลายปี 2017 (และปี 2018 ระบุไว้ในหนังสือ) ไม่เหมือนกับหนังสือเล่มอื่นๆ หนังสือเล่มนี้เน้นไปที่รูปแบบโดยเฉพาะ ไม่ใช่คำแนะนำ (เช่น Mayers) หรือ C++ โดยทั่วไป เช่น Stroustrup ดังนั้นข้อดี/ข้อเสียจึงได้รับการพิจารณาที่นี่จากมุมมองของการเขียนเทมเพลต

บทที่ 7 ทั้งหมดเน้นไปที่หัวข้อนี้ ซึ่งมีชื่อที่มีวาทศิลป์ว่า “ตามคุณค่าหรือโดยการอ้างอิง?” ในบทนี้ ผู้เขียนจะอธิบายวิธีการส่งสัญญาณทั้งหมดอย่างกระชับแต่กระชับพร้อมทั้งข้อดีและข้อเสีย ในทางปฏิบัติไม่ได้ให้การวิเคราะห์ประสิทธิผลที่นี่ และเป็นที่ยอมรับว่า PPSC จะเร็วกว่า PPZ แต่ทั้งหมดนี้ ในตอนท้ายของบท ผู้เขียนแนะนำให้ใช้ PPP เริ่มต้นสำหรับฟังก์ชันเทมเพลต ทำไม เนื่องจากการใช้ลิงก์ พารามิเตอร์เทมเพลตจะแสดงอย่างสมบูรณ์ และไม่มีลิงก์ พารามิเตอร์เหล่านั้นจะ "เสื่อม" ซึ่งส่งผลดีต่อการประมวลผลอาร์เรย์และตัวอักษรสตริง ผู้เขียนเชื่อว่าหาก PPP บางประเภทไม่ได้ผล คุณสามารถใช้ std::ref และ std::cref ได้ตลอดเวลา พูดตามตรงนี่คือคำแนะนำบางส่วน คุณเคยเห็นคนจำนวนมากที่ต้องการใช้ฟังก์ชันข้างต้นหรือไม่

พวกเขาให้คำแนะนำอะไรเกี่ยวกับ PPSC? พวกเขาแนะนำให้ใช้ PPSC เมื่อประสิทธิภาพมีความสำคัญหรือมีอย่างอื่น มีน้ำหนักเหตุผลที่จะไม่ใช้พรรคพลังประชาชน แน่นอนว่าเรากำลังพูดถึงแค่โค้ดสำเร็จรูปเท่านั้น แต่คำแนะนำนี้ขัดแย้งโดยตรงต่อทุกสิ่งที่โปรแกรมเมอร์ได้รับการสอนมาเป็นเวลากว่าทศวรรษ นี่ไม่ใช่แค่คำแนะนำในการพิจารณา PPP เป็นทางเลือก ไม่ใช่ แต่เป็นคำแนะนำที่จะทำให้ PPSC เป็นทางเลือกอื่น

จบการทัวร์หนังสือของเราแล้ว เพราะ... ฉันรู้ว่าไม่มีหนังสือเล่มอื่นใดที่เราควรปรึกษาเกี่ยวกับปัญหานี้ เรามาดูพื้นที่สื่ออื่นกันดีกว่า

ภูมิปัญญาด้านเครือข่าย

เพราะ เราอยู่ในยุคของอินเทอร์เน็ต คุณไม่ควรพึ่งพาภูมิปัญญาทางหนังสือเพียงอย่างเดียว ยิ่งไปกว่านั้น นักเขียนหลายคนที่เคยเขียนหนังสือตอนนี้กลับเขียนบล็อกและละทิ้งหนังสือไป หนึ่งในผู้เขียนเหล่านี้คือ Herb Sutter ซึ่งในเดือนพฤษภาคม 2013 ตีพิมพ์บทความในบล็อกของเขา “GotW #4 Solution: Class Mechanics” ซึ่งแม้จะไม่ได้เน้นไปที่ปัญหาที่เรากำลังพูดถึงทั้งหมด แต่ยังคงพูดถึงประเด็นนั้นอยู่

ดังนั้น ในเวอร์ชันต้นฉบับของบทความ Sutter เพียงย้ำภูมิปัญญาเก่าๆ: "ส่งพารามิเตอร์โดยอ้างอิงกับค่าคงที่" แต่เราจะไม่เห็นบทความเวอร์ชันนี้อีกต่อไป เนื่องจาก บทความนี้มีคำแนะนำที่ตรงกันข้าม: “ ถ้าพารามิเตอร์จะยังคงถูกคัดลอก จากนั้นจึงส่งต่อตามค่า” อีกครั้งที่ฉาวโฉ่ "ถ้า" เหตุใด Sutter จึงเปลี่ยนบทความ และฉันรู้เรื่องนี้ได้อย่างไร จากความคิดเห็น. อ่านความคิดเห็นในบทความของเขาโดยพบว่าน่าสนใจและมีประโยชน์มากกว่าตัวบทความเอง จริงอยู่ที่หลังจากเขียนบทความ ในที่สุด Sutter ก็เปลี่ยนความคิดเห็นของเขาและเขาก็ไม่ให้คำแนะนำเช่นนั้นอีกต่อไป การเปลี่ยนแปลงความคิดเห็นสามารถพบได้ในสุนทรพจน์ของเขาที่ CppCon ในปี 2014: “กลับสู่พื้นฐาน! สิ่งสำคัญของสไตล์ C++ สมัยใหม่" อย่าลืมดูเราจะไปยังลิงค์อินเทอร์เน็ตถัดไป

และต่อไป เราก็มีทรัพยากรการเขียนโปรแกรมหลักแห่งศตวรรษที่ 21: StackOverflow หรือค่อนข้างเป็นคำตอบโดยมีจำนวนปฏิกิริยาเชิงบวกเกิน 1,700 ในขณะที่เขียนบทความนี้ คำถามคือ สำนวนการคัดลอกและสลับคืออะไร และตามที่ชื่อควรจะแนะนำ ไม่ใช่หัวข้อที่เรากำลังดูอยู่ แต่ในการตอบคำถามนี้ผู้เขียนยังได้กล่าวถึงหัวข้อที่เราสนใจด้วย นอกจากนี้เขายังแนะนำให้ใช้ PPZ “หากข้อโต้แย้งนั้นจะถูกคัดลอกต่อไป” (ถึงเวลาแนะนำตัวย่อสำหรับสิ่งนี้เช่นกัน โดยพระเจ้า) และโดยทั่วไป คำแนะนำนี้ดูเหมือนค่อนข้างเหมาะสม ภายในกรอบของคำตอบของเขาและผู้ดำเนินการ= ที่กล่าวถึงในนั้น แต่ผู้เขียนมีเสรีภาพในการให้คำแนะนำดังกล่าวในลักษณะที่กว้างกว่า ไม่ใช่แค่ในกรณีนี้โดยเฉพาะ ยิ่งไปกว่านั้น เขายังไปไกลกว่าเคล็ดลับทั้งหมดที่เราเคยพูดคุยกันก่อนหน้านี้ และเรียกร้องให้ทำเช่นนี้แม้จะอยู่ในโค้ด C++03! อะไร​กระตุ้น​ให้​ผู้​เขียน​ลง​ความ​เห็น​เช่น​นั้น?

เห็นได้ชัดว่าผู้เขียนคำตอบได้รับแรงบันดาลใจหลักจากบทความของผู้เขียนหนังสือคนอื่นและผู้พัฒนานอกเวลาของ Boost.MPL - Dave Abrahams บทความนี้ชื่อ “Want Speed? ผ่านไปอย่างคุ้มค่า” และเผยแพร่ย้อนกลับไปในเดือนสิงหาคม พ.ศ. 2552 กล่าวคือ 2 ปีก่อนการนำ C++11 มาใช้ และการแนะนำความหมายของการย้าย เช่นเดียวกับในกรณีก่อนหน้านี้ ฉันแนะนำให้ผู้อ่านอ่านบทความด้วยตัวเอง แต่ฉันจะให้ข้อโต้แย้งหลัก (อันที่จริงมีเพียงข้อโต้แย้งเดียวเท่านั้น) ที่ Dave ให้ไว้กับ PPZ: คุณต้องใช้ PPZ เนื่องจากการเพิ่มประสิทธิภาพ "ข้ามสำเนา" ทำงานได้ดี ( การลบสำเนา) ซึ่งไม่มีใน PPSC หากคุณอ่านความคิดเห็นในบทความ คุณจะเห็นว่าคำแนะนำที่เขาส่งเสริมนั้นไม่เป็นสากล ซึ่งผู้เขียนเองก็ยืนยันเมื่อตอบคำวิจารณ์จากนักวิจารณ์ อย่างไรก็ตาม บทความนี้มีคำแนะนำที่ชัดเจน (แนวทาง) ในการใช้ PPP หากข้อโต้แย้งจะถูกคัดลอกต่อไป อย่างไรก็ตามถ้าใครสนใจสามารถอ่านบทความ “อยากได้ความเร็ว? อย่า (เสมอ) ผ่านคุณค่า” . ตามที่ชื่อเรื่องควรระบุ บทความนี้เป็นการตอบบทความของ Dave ดังนั้นหากคุณอ่านบทความแรกก็อย่าลืมอ่านบทความนี้ด้วย!

น่าเสียดาย (โชคดีสำหรับบางคน) บทความดังกล่าวและ (ยิ่งกว่านั้น) คำตอบยอดนิยมในไซต์ยอดนิยมทำให้เกิดการใช้เทคนิคที่น่าสงสัยจำนวนมาก (ตัวอย่างเล็กน้อย) เพียงเพราะต้องใช้การเขียนน้อยลง และความเชื่อแบบเก่าก็ไม่สั่นคลอนอีกต่อไป - คุณสามารถอ้างถึง “คำแนะนำยอดนิยมนั้น” ได้เสมอหากคุณถูกผลักให้ติดกับกำแพง ตอนนี้ฉันขอแนะนำให้คุณทำความคุ้นเคยกับแหล่งข้อมูลต่างๆ ที่ให้คำแนะนำในการเขียนโค้ดแก่เรา

เพราะ เนื่องจากขณะนี้มีการโพสต์มาตรฐานและคำแนะนำต่างๆ ทางออนไลน์ ฉันจึงตัดสินใจจัดประเภทส่วนนี้ว่าเป็น "ภูมิปัญญาด้านเครือข่าย" ดังนั้น ฉันอยากจะพูดถึงแหล่งที่มาสองแห่ง จุดประสงค์คือเพื่อทำให้โค้ดของโปรแกรมเมอร์ C++ ดีขึ้นโดยการให้คำแนะนำ (แนวทาง) เกี่ยวกับวิธีการเขียนโค้ดนี้

กฎชุดแรกที่ฉันต้องการพิจารณาคือฟางเส้นสุดท้ายที่บังคับให้ฉันอ่านบทความนี้ ชุดนี้เป็นส่วนหนึ่งของยูทิลิตี้ clang-tidy และไม่มีอยู่ภายนอก เช่นเดียวกับทุกสิ่งที่เกี่ยวข้องกับเสียงดังกราว ยูทิลิตี้นี้ได้รับความนิยมอย่างมากและได้รับการบูรณาการกับ CLion และ Resharper C++ แล้ว (นั่นคือวิธีที่ฉันเจอ) ดังนั้น clang-tydy จึงมีกฎ modernize-pass-by-value ที่ใช้ได้กับ Constructor ที่ยอมรับอาร์กิวเมนต์ผ่าน PPSC กฎนี้แนะนำให้เราแทนที่ PPSC ด้วย PPZ นอกจากนี้ในขณะที่เขียนบทความคำอธิบายกฎนี้มีข้อสังเกตว่ากฎนี้ ลาก่อนใช้ได้กับผู้สร้างเท่านั้น แต่พวกเขา (พวกเขาเป็นใคร) ยินดียินดีรับความช่วยเหลือจากผู้ที่ขยายกฎนี้ไปยังหน่วยงานอื่น ในคำอธิบายยังมีลิงก์ไปยังบทความของ Dave ด้วยซึ่งชัดเจนว่าขามาจากไหน

สุดท้ายนี้ เพื่อสรุปการทบทวนภูมิปัญญาของผู้อื่นและความคิดเห็นที่เชื่อถือได้ ฉันขอแนะนำให้คุณดูแนวทางอย่างเป็นทางการสำหรับการเขียนโค้ด C++: C++ Core Guidelines ซึ่งมีบรรณาธิการหลักคือ Herb Sutter และ Bjarne Stroustrup (ไม่แย่ใช่ไหม?) ดังนั้นคำแนะนำเหล่านี้จึงมีกฎต่อไปนี้: "สำหรับพารามิเตอร์ "ใน" ให้ส่งประเภทที่คัดลอกราคาถูกตามค่าและอื่น ๆ โดยอ้างอิงกับ const" ซึ่งทำซ้ำภูมิปัญญาเก่าอย่างสมบูรณ์: PPSK ทุกที่และ PPP สำหรับวัตถุขนาดเล็ก เคล็ดลับนี้จะสรุปทางเลือกต่างๆ ที่ควรพิจารณา ในกรณีที่การส่งผ่านอาร์กิวเมนต์จำเป็นต้องมีการปรับให้เหมาะสม. แต่ PPZ ไม่รวมอยู่ในรายการทางเลือก!

เนื่องจากฉันไม่มีแหล่งอื่นที่น่าสนใจ ฉันจึงเสนอให้ดำเนินการวิเคราะห์โดยตรงของการส่งสัญญาณทั้งสองวิธี

การวิเคราะห์

ข้อความก่อนหน้าทั้งหมดเขียนในลักษณะที่ค่อนข้างแปลกสำหรับฉัน: ฉันเสนอความคิดเห็นของผู้อื่นและพยายามไม่แสดงความคิดเห็นของตัวเอง (ฉันรู้ว่ามันออกมาไม่ดี) ส่วนใหญ่เนื่องจากความคิดเห็นของผู้อื่นและเป้าหมายของฉันคือการทำให้ภาพรวมโดยย่อของพวกเขา ฉันจึงเลื่อนการพิจารณาโดยละเอียดเกี่ยวกับข้อโต้แย้งบางอย่างที่ฉันพบในผู้เขียนคนอื่น ในส่วนนี้ ฉันจะไม่อ้างถึงเจ้าหน้าที่และแสดงความคิดเห็น ในที่นี้ เราจะดูข้อดีและข้อเสียเชิงวัตถุประสงค์ของ PPSC และ PPZ ซึ่งจะปรุงรสด้วยการรับรู้ส่วนตัวของฉัน แน่นอนว่าสิ่งที่กล่าวถึงก่อนหน้านี้บางส่วนจะถูกทำซ้ำ แต่อนิจจานี่คือโครงสร้างของบทความนี้

กปปส.มีข้อได้เปรียบหรือไม่?

ดังนั้น ก่อนที่จะพิจารณาข้อโต้แย้งทั้งสำหรับและคัดค้าน ฉันเสนอให้ดูว่าอะไรและในกรณีใดบ้างที่ข้อได้เปรียบที่ส่งผ่านคุณค่านั้นมอบให้เรา สมมติว่าเรามีชั้นเรียนเช่นนี้:

Class CopyMover ( สาธารณะ: void setByValuer(บัญชี byValuer) ( m_ByValuer = std::move(byValuer); ) void setByRefer(const Accounter& byRefer) ( m_ByRefer = byRefer; ) void setByValuerAndNotMover(บัญชี byValuerAndNotMover) ( m_ByValuerAndNotMover = โดย Val uerAndNotMover; ) เป็นโมฆะ setRvaluer (บัญชี&& rvaluer) ( m_Rvaluer = std::move(rvaluer); ) );

แม้ว่าตามวัตถุประสงค์ของบทความนี้ เราจะสนใจเพียงสองฟังก์ชันแรกเท่านั้น แต่ฉันได้รวมสี่ตัวเลือกไว้เพื่อใช้เป็นจุดตัดกัน

คลาส Accounter เป็นคลาสง่ายๆ ที่นับจำนวนครั้งที่มีการคัดลอก/ย้าย และในคลาส CopyMover เราได้ใช้ฟังก์ชันที่ช่วยให้พิจารณาตัวเลือกต่อไปนี้:

    การย้ายผ่านการโต้แย้ง

    ผ่านค่า ตามด้วย กำลังคัดลอกผ่านการโต้แย้ง

ทีนี้ ถ้าเราส่งค่า lvalue ไปยังแต่ละฟังก์ชันเหล่านี้ ตัวอย่างเช่น:

นักบัญชีโดยอ้างอิง; นักบัญชีโดยValuer; นักบัญชีโดยValuerAndNotMover; CopyMover CopyMover; copyMover.setByRefer (โดยอ้างอิง); copyMover.setByValuer(byValuer); copyMover.setByValuerAndNotMover(โดยValuerAndNotMover);

จากนั้นเราจะได้ผลลัพธ์ดังต่อไปนี้:

ผู้ชนะที่ชัดเจนคือ PPSC เพราะ... ให้เพียงหนึ่งสำเนา ในขณะที่ PPZ ให้หนึ่งสำเนาและหนึ่งการเคลื่อนไหว

ตอนนี้เรามาลองส่งค่า rvalue:

CopyMover CopyMover; copyMover.setByRefer(บัญชี()); copyMover.setByValuer(บัญชี()); copyMover.setByValuerAndNotMover(บัญชี()); copyMover.setRvaluer(บัญชี());

เราได้รับสิ่งต่อไปนี้:

ไม่มีผู้ชนะที่ชัดเจนที่นี่ เพราะ... ทั้ง PPZ และ PPSK มีการดำเนินการเพียงครั้งเดียว แต่เนื่องจาก PPZ ใช้การเคลื่อนไหว และ PPSK ใช้การคัดลอก เราจึงสามารถมอบชัยชนะให้กับ PPZ ได้

แต่การทดลองของเราไม่ได้จบเพียงแค่นั้น มาเพิ่มฟังก์ชันต่อไปนี้เพื่อจำลองการโทรทางอ้อม (โดยผ่านอาร์กิวเมนต์ที่ตามมา):

เป็นโมฆะ setByValuer(บัญชี byValuer, CopyMover& copyMover) ( copyMover.setByValuer(std::move(byValuer)); ) เป็นโมฆะ setByRefer(const Accounter& byRefer, CopyMover& copyMover) ( copyMover.setByRefer(byRefer); ) ...

เราจะใช้มันในลักษณะเดียวกับที่เราทำโดยไม่มีพวกมัน ดังนั้นฉันจะไม่ทำซ้ำโค้ดนี้ (ดูในพื้นที่เก็บข้อมูลหากจำเป็น) ดังนั้นสำหรับ lvalue ผลลัพธ์จะเป็นดังนี้:

โปรดทราบว่า PPSC เพิ่มช่องว่างกับ PPZ โดยเหลือเพียงสำเนาเดียว ในขณะที่ PPZ มีการดำเนินการมากถึง 3 รายการแล้ว (การเคลื่อนไหวอีกครั้งหนึ่ง)!

ตอนนี้เราส่งค่า rvalue และรับผลลัพธ์ต่อไปนี้:

ตอนนี้ PPZ มี 2 การเคลื่อนไหว และ PPSC ยังคงมีหนึ่งสำเนา ตอนนี้เป็นไปได้ไหมที่จะเสนอชื่อ PPZ เป็นผู้ชนะ? ไม่ เพราะ. หากการเคลื่อนไหวครั้งหนึ่งไม่ควรแย่ไปกว่าหนึ่งสำเนา เราไม่สามารถพูดแบบเดียวกันได้เกี่ยวกับการเคลื่อนไหว 2 ครั้ง ดังนั้นจะไม่มีผู้ชนะในตัวอย่างนี้

พวกเขาอาจคัดค้านฉัน: “ผู้เขียน คุณมีความเห็นเอนเอียงและคุณกำลังดึงสิ่งที่เป็นประโยชน์ต่อคุณเข้ามา แม้แต่ 2 กระบวนท่าก็ยังถูกกว่าการคัดลอก!” ฉันไม่สามารถเห็นด้วยกับข้อความนี้ รวมๆแล้ว, เพราะ การย้ายจะเร็วกว่าการคัดลอกมากน้อยเพียงใดนั้นขึ้นอยู่กับคลาสเฉพาะ แต่เราจะดูการย้ายที่ "ถูก" ในส่วนอื่น

ที่นี่เราได้พูดถึงสิ่งที่น่าสนใจ: เราได้เพิ่มการโทรทางอ้อมหนึ่งครั้ง และ PPP ได้เพิ่มการดำเนินการเดียวใน "น้ำหนัก" ฉันคิดว่าคุณไม่จำเป็นต้องมีประกาศนียบัตรจาก MSTU เพื่อทำความเข้าใจว่ายิ่งเรามีการโทรทางอ้อมมากเท่าใด การดำเนินการก็จะยิ่งมากขึ้นเมื่อใช้ PPZ ในขณะที่ PPSC หมายเลขจะไม่เปลี่ยนแปลง

ทุกสิ่งที่กล่าวถึงข้างต้นไม่น่าจะเปิดเผยแก่ใครเลย เราอาจไม่ได้ทำการทดลองด้วยซ้ำ ตัวเลขเหล่านี้ทั้งหมดควรจะชัดเจนสำหรับโปรแกรมเมอร์ C++ ส่วนใหญ่เมื่อมองแวบแรก จริงอยู่ มีประเด็นหนึ่งที่สมควรได้รับการชี้แจง: ทำไมในกรณีของค่า rvalue นั้น PZ จึงไม่มีการคัดลอก (หรือการเคลื่อนไหวอื่น) แต่มีเพียงการเคลื่อนไหวเดียวเท่านั้น

เราได้ดูความแตกต่างในการส่งข้อมูลระหว่าง PPZ และ PPSC โดยการสังเกตจำนวนสำเนาและการย้ายโดยตรง แม้ว่าจะเห็นได้ชัดว่าข้อได้เปรียบของ PPZ เหนือ PPSC แม้ในตัวอย่างนี้ก็คือ หากพูดอย่างอ่อนโยน ไม่แน่นอนว่าฉันยังคงทำท่าทำท่าเล็กน้อยในการสรุปต่อไปนี้: ถ้าเรายังคงคัดลอกอาร์กิวเมนต์ของฟังก์ชัน ก็สมเหตุสมผลที่จะพิจารณาส่งอาร์กิวเมนต์ไปยังฟังก์ชันตามค่า เหตุใดฉันจึงได้ข้อสรุปนี้ เพื่อเลื่อนไปยังส่วนถัดไปได้อย่างราบรื่น

หากเราคัดลอก...

ดังนั้นเราจึงมาถึงสุภาษิต "ถ้า" ข้อโต้แย้งส่วนใหญ่ที่เราพบไม่ได้เรียกร้องให้มีการนำ PPP ไปใช้แบบสากลแทน PPSC พวกเขาเพียงเรียกร้องให้ทำเช่นนั้น "หากข้อโต้แย้งนั้นถูกคัดลอกต่อไป" ถึงเวลาค้นหาว่ามีอะไรผิดปกติกับข้อโต้แย้งนี้

ฉันต้องการเริ่มต้นด้วยคำอธิบายเล็กน้อยเกี่ยวกับวิธีเขียนโค้ด ระยะหลังนี้ กระบวนการเขียนโค้ดของฉันกลายมาเป็น TDD มากขึ้นเรื่อยๆ เช่น การเขียนวิธีการเรียนใดๆ จะเริ่มต้นด้วยการเขียนแบบทดสอบซึ่งมีวิธีนี้ปรากฏอยู่ ดังนั้นเมื่อเริ่มเขียน Test แล้วสร้าง Method หลังจากเขียน Test แล้ว ผมก็ยังไม่รู้ว่าจะ Copy อาร์กิวเมนต์ได้หรือเปล่า แน่นอนว่าไม่ใช่ว่าทุกฟังก์ชันจะถูกสร้างขึ้นในลักษณะนี้ บ่อยครั้ง แม้ในกระบวนการเขียนการทดสอบ คุณก็รู้แน่ชัดว่าจะมีการนำไปใช้งานประเภทใด แต่สิ่งนี้ไม่ได้เกิดขึ้นเสมอไป!

บางคนอาจคัดค้านฉันว่าไม่สำคัญว่าวิธีการเขียนในตอนแรกจะเป็นอย่างไร เราสามารถเปลี่ยนวิธีการส่งผ่านข้อโต้แย้งได้เมื่อวิธีการนั้นเป็นรูปเป็นร่าง และมันชัดเจนสำหรับเราว่าเกิดอะไรขึ้นที่นั่น (เช่น ไม่ว่าเราจะคัดลอกหรือ ไม่ ). ฉันเห็นด้วยบางส่วนกับสิ่งนี้ - แน่นอนคุณสามารถทำได้ด้วยวิธีนี้ แต่สิ่งนี้เกี่ยวข้องกับเราในเกมแปลก ๆ บางประเภทที่เราต้องเปลี่ยนอินเทอร์เฟซเพียงเพราะการใช้งานมีการเปลี่ยนแปลง ซึ่งนำเราไปสู่ภาวะที่กลืนไม่เข้าคายไม่ออกต่อไป

ปรากฎว่าเราปรับเปลี่ยน (หรือวางแผน) อินเทอร์เฟซตามวิธีการใช้งาน ฉันไม่คิดว่าตัวเองเป็นผู้เชี่ยวชาญใน OOP และการคำนวณทางทฤษฎีอื่น ๆ ของสถาปัตยกรรมซอฟต์แวร์ แต่การกระทำดังกล่าวขัดแย้งกับกฎพื้นฐานอย่างชัดเจนเมื่อการใช้งานไม่ควรส่งผลกระทบต่ออินเทอร์เฟซ แน่นอนว่ารายละเอียดการใช้งานบางอย่าง (ไม่ว่าจะเป็นคุณสมบัติของภาษาหรือแพลตฟอร์มเป้าหมาย) ยังคงรั่วไหลผ่านอินเทอร์เฟซไม่ทางใดก็ทางหนึ่ง แต่คุณควรพยายามลดหรือเพิ่มจำนวนของสิ่งเหล่านี้

ขอพระเจ้าอวยพรเขา ให้เราไปตามเส้นทางนี้และยังคงเปลี่ยนอินเทอร์เฟซ ขึ้นอยู่กับสิ่งที่เราทำในการใช้งานในแง่ของการคัดลอกอาร์กิวเมนต์ สมมติว่าเราเขียนวิธีนี้:

เป็นโมฆะ setName(ชื่อชื่อ) ( m_Name = move(name); )

และยืนยันการเปลี่ยนแปลงของเรากับพื้นที่เก็บข้อมูล เมื่อเวลาผ่านไป ผลิตภัณฑ์ซอฟต์แวร์ของเราได้รับฟังก์ชันใหม่ กรอบงานใหม่ถูกรวมเข้าด้วยกัน และงานก็เกิดขึ้นจากการแจ้งให้โลกภายนอกทราบเกี่ยวกับการเปลี่ยนแปลงในชั้นเรียนของเรา เหล่านั้น. เราจะเพิ่มกลไกการแจ้งเตือนให้กับวิธีการของเรา ปล่อยให้มันคล้ายกับสัญญาณ Qt:

เป็นโมฆะ setName(ชื่อชื่อ) ( m_Name = move(name); ปล่อย nameChanged(m_Name); )

มีปัญหากับรหัสนี้หรือไม่? กิน. ทุกครั้งที่โทรหา setName เราจะส่งสัญญาณ ดังนั้นสัญญาณจะถูกส่งแม้ในเวลาใดก็ตาม ความหมาย m_Name ไม่มีการเปลี่ยนแปลง นอกจากปัญหาด้านประสิทธิภาพแล้ว สถานการณ์นี้อาจนำไปสู่การวนซ้ำไม่สิ้นสุดเนื่องจากโค้ดที่ได้รับการแจ้งเตือนข้างต้นมาเรียก setName เพื่อหลีกเลี่ยงปัญหาเหล่านี้ วิธีการดังกล่าวมักมีลักษณะดังนี้:

เป็นโมฆะ setName(ชื่อชื่อ) ( if(name == m_Name) return; m_Name = move(name); emit nameChanged(m_Name); )

เราได้กำจัดปัญหาที่อธิบายไว้ข้างต้น แต่ตอนนี้กฎ "ถ้าเราคัดลอกต่อไป ... " ของเราล้มเหลว - ไม่มีการคัดลอกอาร์กิวเมนต์แบบไม่มีเงื่อนไขอีกต่อไป ตอนนี้เราจะคัดลอกเฉพาะในกรณีที่มีการเปลี่ยนแปลงเท่านั้น! แล้วเราควรทำอย่างไรตอนนี้? เปลี่ยนอินเทอร์เฟซ? เอาล่ะ มาเปลี่ยนอินเทอร์เฟซของคลาสเนื่องจากการแก้ไขนี้กันดีกว่า จะเกิดอะไรขึ้นถ้าคลาสของเราสืบทอดวิธีนี้จากอินเทอร์เฟซแบบนามธรรม? มาเปลี่ยนที่นั่นด้วย! มีการเปลี่ยนแปลงมากเพราะการดำเนินการเปลี่ยนแปลงหรือไม่?

พวกเขาอาจคัดค้านฉันอีกครั้ง ผู้เขียนกล่าวว่า ทำไมคุณถึงพยายามประหยัดเงินในการแข่งขัน ในเมื่อเงื่อนไขนี้จะได้ผล? ใช่แล้ว การโทรส่วนใหญ่จะเป็นเท็จ! มีความมั่นใจในเรื่องนี้บ้างไหม? ที่ไหน? และถ้าฉันตัดสินใจที่จะประหยัดค่าแข่งขัน ความจริงที่ว่าเราใช้ PPZ เป็นผลมาจากการประหยัดดังกล่าวไม่ใช่หรือ? ฉันแค่สานต่อ "แนวปาร์ตี้" ที่สนับสนุนประสิทธิภาพ

คอนสตรัคเตอร์

มาดู Constructor สั้นๆ กัน โดยเฉพาะอย่างยิ่งเมื่อมีกฎพิเศษสำหรับพวกมันใน clang-tidy ซึ่งยังใช้ไม่ได้กับวิธี/ฟังก์ชันอื่นๆ สมมติว่าเรามีชั้นเรียนเช่นนี้:

คลาส JustClass ( สาธารณะ: JustClass(const string& justString): m_JustString(justString) ( ) ส่วนตัว: string m_JustString; );

แน่นอนว่าพารามิเตอร์ถูกคัดลอก และ clang-tidy จะบอกเราว่าเป็นความคิดที่ดีที่จะเขียน Constructor ใหม่ดังนี้:

JustClass(สตริง justString): m_JustString(ย้าย(justString)) ( )

และพูดตามตรงว่ามันยากสำหรับฉันที่จะโต้แย้งที่นี่ - ท้ายที่สุดแล้วเราก็ลอกเลียนแบบอยู่เสมอ และบ่อยครั้งที่สุด เมื่อเราส่งบางสิ่งผ่าน Constructor เราจะคัดลอกมัน แต่บ่อยครั้งไม่ได้หมายความว่าเสมอไป นี่เป็นอีกตัวอย่างหนึ่ง:

Class TimeSpan ( สาธารณะ: TimeSpan(DateTime start, DateTime end) ( if(start > end) Throw InvalidTimeSpan(); m_Start = move(start); m_End = move(end); ) ส่วนตัว: DateTime m_Start; DateTime m_End; );

ที่นี่เราไม่ได้คัดลอกเสมอไป แต่เฉพาะเมื่อมีการแสดงวันที่อย่างถูกต้องเท่านั้น แน่นอนว่าในกรณีส่วนใหญ่จะเป็นเช่นนั้น แต่ ไม่เสมอ.

คุณสามารถยกตัวอย่างอื่นได้ แต่คราวนี้ไม่มีโค้ด ลองนึกภาพคุณมีคลาสที่รับวัตถุขนาดใหญ่ คลาสนี้มีมานานแล้ว และตอนนี้ก็ถึงเวลาอัปเดตการใช้งานแล้ว เราตระหนักดีว่าเราต้องการโรงงานขนาดใหญ่ไม่เกินครึ่งหนึ่ง (ซึ่งมีการเติบโตอย่างต่อเนื่องในช่วงหลายปีที่ผ่านมา) และอาจน้อยกว่านั้นด้วยซ้ำ เราสามารถทำอะไรบางอย่างเกี่ยวกับเรื่องนี้โดยส่งผ่านค่าได้หรือไม่? ไม่ เราไม่สามารถดำเนินการใดๆ ได้ เนื่องจากระบบจะยังคงสร้างสำเนาไว้ แต่ถ้าเราใช้ PPSC เราก็จะเปลี่ยนสิ่งที่เราทำ ข้างในนักออกแบบ และนี่คือประเด็นสำคัญ: การใช้ PPSC เราควบคุมว่าจะเกิดอะไรขึ้นและเมื่อใดในการใช้งานฟังก์ชันของเรา (ตัวสร้าง) แต่ถ้าเราใช้ PPZ เราจะสูญเสียการควบคุมการคัดลอก

คุณจะได้อะไรจากส่วนนี้? ความจริงที่ว่าข้อโต้แย้ง "ถ้าเราคัดลอกต่อไป..." เป็นเรื่องที่ถกเถียงกันมากเพราะว่า เราไม่รู้เสมอไปว่าเราจะคัดลอกอะไร และแม้ว่าเราจะรู้ เราก็มักจะไม่แน่ใจว่าสิ่งนี้จะดำเนินต่อไปในอนาคต

การย้ายมีราคาถูก

นับตั้งแต่วินาทีที่ความหมายของการเคลื่อนไหวปรากฏขึ้น มันก็เริ่มมีอิทธิพลอย่างมากต่อวิธีการเขียนโค้ด C++ สมัยใหม่ และเมื่อเวลาผ่านไป อิทธิพลนี้ก็ทวีความรุนแรงมากขึ้นเท่านั้น ไม่น่าแปลกใจเลยที่การเคลื่อนไหวเป็นเช่นนั้น ราคาถูกเมื่อเทียบกับการคัดลอก แต่มันคืออะไร? จริงหรือที่การเคลื่อนไหวนั้น เสมอศัลยกรรมราคาถูก? นี่คือสิ่งที่เราจะพยายามค้นหาในส่วนนี้

วัตถุขนาดใหญ่แบบไบนารี

เริ่มต้นด้วยตัวอย่างเล็กๆ น้อยๆ สมมติว่าเรามีคลาสต่อไปนี้:

โครงสร้าง Blob ( std::array ข้อมูล; );

สามัญ หยด(BDO, English BLOB) ซึ่งสามารถใช้ได้ในสถานการณ์ต่างๆ มาดูกันว่าเราจะเสียค่าใช้จ่ายเท่าใดในการผ่านการอ้างอิงและตามค่า BDO ของเราจะถูกใช้ดังนี้:

พื้นที่เก็บข้อมูลโมฆะ::setBlobByRef(const Blob& blob) ( m_Blob = blob; ) พื้นที่เก็บข้อมูลเป็นโมฆะ::setBlobByVal(Blob blob) ( m_Blob = move(blob); )

และเราจะเรียกฟังก์ชันเหล่านี้ดังนี้:

Const Blob หยด (); พื้นที่จัดเก็บ; storage.setBlobByRef(หยด); storage.setBlobByVal(หยด);

โค้ดสำหรับตัวอย่างอื่นๆ จะเหมือนกันกับโค้ดนี้ เฉพาะกับชื่อและประเภทที่แตกต่างกัน ดังนั้นฉันจะไม่ยกให้สำหรับตัวอย่างที่เหลือ - ทุกอย่างอยู่ในที่เก็บ

ก่อนที่เราจะไปยังการวัด เรามาลองทำนายผลลัพธ์กันก่อน ดังนั้นเราจึงมีอาร์เรย์ std::array ขนาด 4 KB ที่เราต้องการเก็บไว้ในออบเจ็กต์คลาส Storage ตามที่เราทราบก่อนหน้านี้ สำหรับ PPSC เราจะมีหนึ่งสำเนา ในขณะที่สำหรับ PPZ เราจะมีหนึ่งสำเนาและหนึ่งการเคลื่อนไหว จากข้อเท็จจริงที่ว่ามันเป็นไปไม่ได้ที่จะย้ายอาร์เรย์ จะมีสำเนา 2 ชุดสำหรับ PPZ เทียบกับอีกชุดสำหรับ PPSC เหล่านั้น. เราสามารถคาดหวังประสิทธิภาพที่เหนือกว่าสองเท่าของ PPSC

ตอนนี้เรามาดูผลการทดสอบกันดีกว่า:

การทดสอบนี้และการทดสอบที่ตามมาทั้งหมดรันบนเครื่องเดียวกันโดยใช้ MSVS 2017 (15.7.2) และแฟล็ก /O2

การปฏิบัติใกล้เคียงกับข้อสันนิษฐาน - การส่งผ่านค่าจะมีราคาแพงกว่า 2 เท่า เนื่องจากสำหรับอาร์เรย์ การย้ายจะเทียบเท่ากับการคัดลอกโดยสิ้นเชิง

เส้น

ลองดูอีกตัวอย่างหนึ่ง std::string ปกติ เราคาดหวังอะไรได้บ้าง? เรารู้ (ฉันพูดถึงเรื่องนี้ในบทความ) ว่าการใช้งานสมัยใหม่แยกความแตกต่างระหว่างสตริงสองประเภท: แบบสั้น (ประมาณ 16 อักขระ) และแบบยาว (ประเภทที่ยาวกว่าแบบสั้น) สำหรับบัฟเฟอร์แบบสั้น จะใช้บัฟเฟอร์ภายใน ซึ่งเป็นอาร์เรย์ C ปกติของ char แต่บัฟเฟอร์แบบยาวจะถูกวางไว้บนฮีปแล้ว เราไม่สนใจประโยคสั้นๆ เพราะ... ผลลัพธ์จะเหมือนกับ BDO ดังนั้นเรามาเน้นที่เส้นยาวกันดีกว่า

ดังนั้นการมีสายยาวก็ชัดเจนว่าการย้ายควรจะค่อนข้างถูก (แค่เลื่อนตัวชี้) ดังนั้นคุณสามารถวางใจได้ว่าการย้ายสายไม่ควรส่งผลกระทบต่อผลลัพธ์เลย และ PPZ ควรให้ผลลัพธ์ ไม่เลวร้ายไปกว่า กปปส. มาตรวจสอบในทางปฏิบัติและรับผลลัพธ์ต่อไปนี้:

เราจะมาอธิบาย "ปรากฏการณ์" นี้กันต่อ แล้วจะเกิดอะไรขึ้นเมื่อเราคัดลอกสตริงที่มีอยู่ไปลงในสตริงที่มีอยู่แล้ว? ลองดูตัวอย่างเล็กน้อย:

สตริงแรก (64, "C"); สตริงที่สอง (64, "N"); //... วินาที = ครั้งแรก;

เรามีสตริง 64 อักขระสองตัว ดังนั้นบัฟเฟอร์ภายในจึงไม่เพียงพอเมื่อสร้างสตริงเหล่านั้น ส่งผลให้ทั้งสองสตริงได้รับการจัดสรรบนฮีป ตอนนี้เราคัดลอกก่อนถึงวินาที เพราะ ขนาดแถวของเราเท่ากัน เห็นได้ชัดว่ามีพื้นที่เพียงพอในหน่วยวินาทีเพื่อรองรับข้อมูลทั้งหมดจากแถวแรก ดังนั้น ที่สอง = อันดับแรก; จะเป็น memcpy ซ้ำซากไม่มีอะไรมาก แต่ถ้าเราดูตัวอย่างที่ปรับเปลี่ยนเล็กน้อย:

สตริงแรก (64, "C"); สตริงที่สอง = อันดับแรก;

จากนั้นจะไม่มีการเรียกไปยังโอเปอเรเตอร์= อีกต่อไป แต่จะมีการเรียกตัวสร้างการคัดลอก เพราะ เนื่องจากเรากำลังติดต่อกับ Constructor จึงไม่มีหน่วยความจำอยู่ในนั้น ต้องเลือกก่อนแล้วจึงคัดลอกก่อน เหล่านั้น. นี่คือการจัดสรรหน่วยความจำแล้ว memcpy ดังที่คุณและฉันทราบ การจัดสรรหน่วยความจำบนฮีปส่วนกลางมักเป็นการดำเนินการที่มีราคาแพง ดังนั้นการคัดลอกจากตัวอย่างที่สองจะมีราคาแพงกว่าการคัดลอกจากตัวอย่างแรก การจัดสรรหน่วยความจำต่อฮีปมีราคาแพงกว่า

เกี่ยวอะไรกับหัวข้อของเรา? วิธีที่ตรงที่สุด เนื่องจากตัวอย่างแรกแสดงให้เห็นอย่างชัดเจนว่าเกิดอะไรขึ้นกับ PPSC และตัวอย่างที่สองแสดงให้เห็นว่าเกิดอะไรขึ้นกับ PPZ: สำหรับ PPZ แถวใหม่จะถูกสร้างขึ้นเสมอ ในขณะที่สำหรับ PPSC แถวที่มีอยู่จะถูกนำมาใช้ซ้ำ คุณได้เห็นความแตกต่างของเวลาดำเนินการแล้ว ดังนั้นจึงไม่มีอะไรต้องเพิ่มที่นี่

อีกครั้งที่เรากำลังเผชิญกับข้อเท็จจริงที่ว่าเมื่อใช้ PPP เราทำงานโดยไม่มีบริบท ดังนั้นจึงไม่สามารถใช้ผลประโยชน์ทั้งหมดที่สามารถให้ได้ได้ และหากก่อนหน้านี้เราให้เหตุผลในแง่ของการเปลี่ยนแปลงในอนาคตทางทฤษฎี เรากำลังสังเกตเห็นความล้มเหลวอย่างเป็นรูปธรรมในด้านประสิทธิภาพการผลิต

แน่นอนว่าอาจมีบางคนคัดค้านฉันว่าเชือกนั้นแยกออกจากกัน และประเภทส่วนใหญ่ก็ไม่ได้ผลเช่นนั้น ซึ่งฉันสามารถตอบได้ดังต่อไปนี้: ทุกอย่างที่อธิบายไว้ก่อนหน้านี้จะเป็นจริงสำหรับคอนเทนเนอร์ใด ๆ ที่จัดสรรหน่วยความจำในฮีปทันทีสำหรับชุดขององค์ประกอบ นอกจากนี้ ใครจะรู้ว่ามีการใช้การเพิ่มประสิทธิภาพตามบริบทอื่นๆ ใดบ้างในประเภทอื่นๆ

คุณควรนำอะไรไปจากส่วนนี้? ความจริงที่ว่าแม้ว่าการย้ายจะมีราคาถูกจริงๆ ไม่ได้หมายความว่าการแทนที่การคัดลอกด้วยการคัดลอก+การย้ายจะให้ผลลัพธ์ที่เทียบเคียงได้เสมอในแง่ของประสิทธิภาพ

ประเภทที่ซับซ้อน

สุดท้ายนี้ เรามาดูประเภทที่จะประกอบด้วยหลายวัตถุกัน ให้นี่คือคลาส Person ซึ่งประกอบด้วยข้อมูลที่มีอยู่ในตัวบุคคล โดยปกติจะเป็นชื่อ นามสกุล รหัสไปรษณีย์ ฯลฯ ของคุณ คุณสามารถแสดงทั้งหมดนี้ในรูปแบบสตริงได้ และสมมติว่าสตริงที่คุณใส่ลงในฟิลด์ของคลาส Person มีแนวโน้มที่จะสั้น แม้ว่าฉันเชื่อว่าในชีวิตจริง การวัดสายสั้นจะมีประโยชน์มากที่สุด แต่เรายังคงดูสายที่มีขนาดต่างกันเพื่อให้ได้ภาพที่สมบูรณ์ยิ่งขึ้น

ฉันจะใช้บุคคลที่มี 10 ฟิลด์ด้วย แต่สำหรับสิ่งนี้ ฉันจะไม่สร้าง 10 ฟิลด์โดยตรงในเนื้อหาของชั้นเรียน การใช้งาน Person จะซ่อนคอนเทนเนอร์ไว้ในส่วนลึก - ทำให้สะดวกยิ่งขึ้นในการเปลี่ยนพารามิเตอร์การทดสอบ ในทางปฏิบัติโดยไม่เบี่ยงเบนไปจากวิธีการทำงานหาก Person เป็นคลาสจริง อย่างไรก็ตาม มีการติดตั้งใช้งานและคุณสามารถตรวจสอบโค้ดและบอกฉันได้ตลอดเวลาว่าฉันทำอะไรผิดหรือไม่

ไปกันเลย: บุคคลที่มีประเภท 10 ฟิลด์ string ซึ่งเราถ่ายโอนโดยใช้ PPSC และ PPZ ไปยัง Storage :

อย่างที่คุณเห็น เรามีความแตกต่างอย่างมากในด้านประสิทธิภาพ ซึ่งไม่น่าสร้างความประหลาดใจให้กับผู้อ่านหลังจากหัวข้อที่แล้ว ฉันยังเชื่อด้วยว่าคลาส Person นั้น "จริง" เพียงพอแล้วที่ผลลัพธ์ดังกล่าวจะไม่ถูกมองว่าเป็นนามธรรม

อย่างไรก็ตาม ตอนที่ฉันกำลังเตรียมบทความนี้ ฉันได้เตรียมตัวอย่างอื่นไว้ด้วย: คลาสที่ใช้วัตถุ std::function หลายตัว ตามความคิดของฉัน มันควรจะแสดงความได้เปรียบในประสิทธิภาพของ PPSC เหนือ PPZ ด้วย แต่กลับกลายเป็นตรงกันข้าม! แต่ฉันไม่ได้ยกตัวอย่างนี้ในที่นี้ ไม่ใช่เพราะฉันไม่ชอบผลลัพธ์ แต่เป็นเพราะฉันไม่มีเวลาคิดว่าเหตุใดจึงได้รับผลลัพธ์ดังกล่าว อย่างไรก็ตามมีโค้ดอยู่ในที่เก็บ (เครื่องพิมพ์) การทดสอบ - เช่นกัน ถ้าใครอยากทราบผลการวิจัยก็ยินดีเป็นอย่างยิ่ง ฉันวางแผนที่จะกลับมาที่ตัวอย่างนี้ในภายหลัง และหากไม่มีใครเผยแพร่ผลลัพธ์เหล่านี้ต่อหน้าฉัน ฉันจะพิจารณาในบทความแยกต่างหาก

ผลลัพธ์

ดังนั้นเราจึงดูข้อดีข้อเสียต่างๆ ของการส่งผ่านค่าและส่งผ่านโดยอ้างอิงกับค่าคงที่ เราดูตัวอย่างบางส่วนและดูประสิทธิภาพของทั้งสองวิธีในตัวอย่างนี้ แน่นอนว่าบทความนี้ไม่สามารถและไม่ครบถ้วนสมบูรณ์ แต่ในความคิดของฉัน มีข้อมูลเพียงพอที่จะตัดสินใจอย่างอิสระและมีข้อมูลว่าควรใช้วิธีใดดีที่สุด บางคนอาจคัดค้าน: “ทำไมต้องใช้วิธีเดียว มาเริ่มงานกันเลย!” แม้ว่าฉันจะเห็นด้วยกับวิทยานิพนธ์นี้โดยทั่วไป แต่ฉันก็ไม่เห็นด้วยกับสิ่งนี้ในสถานการณ์นี้ ฉันเชื่อว่ามีวิธีเดียวเท่านั้นในการโต้แย้งในภาษาหนึ่ง ซึ่งใช้เป็นค่าเริ่มต้น.

ค่าเริ่มต้นหมายถึงอะไร? ซึ่งหมายความว่าเมื่อฉันเขียนฟังก์ชัน ฉันไม่คิดว่าจะผ่านอาร์กิวเมนต์ได้อย่างไร ฉันแค่ใช้ "ค่าเริ่มต้น" ภาษา C++ เป็นภาษาที่ค่อนข้างซับซ้อนซึ่งหลายคนหลีกเลี่ยง และในความคิดของฉันความซับซ้อนนั้นไม่ได้เกิดจากความซับซ้อนของโครงสร้างภาษาที่มีอยู่ในภาษามากนัก (โปรแกรมเมอร์ทั่วไปอาจไม่เคยเจอมันเลย) แต่จากความจริงที่ว่าภาษาทำให้คุณคิดมาก: ฉันว่างไหม อัพหน่วยความจำ, ใช้งานฟังก์ชั่นนี้ที่นี่แพงไหม และอื่นๆ

โปรแกรมเมอร์จำนวนมาก (C, C++ และอื่นๆ) ไม่ไว้วางใจและกลัว C++ ที่เริ่มปรากฏหลังปี 2011 ฉันได้ยินคำวิพากษ์วิจารณ์มากมายว่าภาษามีความซับซ้อนมากขึ้น มีเพียง “กูรู” เท่านั้นที่สามารถเขียนลงไปได้ เป็นต้น โดยส่วนตัวแล้วฉันเชื่อว่าไม่เป็นเช่นนั้น - ในทางกลับกัน คณะกรรมการทุ่มเทเวลาอย่างมากเพื่อทำให้ภาษาเป็นมิตรกับผู้เริ่มต้นมากขึ้น และเพื่อให้โปรแกรมเมอร์จำเป็นต้องคิดถึงคุณลักษณะของภาษาให้น้อยลง ท้ายที่สุดแล้ว ถ้าเราไม่ต้องดิ้นรนกับภาษา เราก็จะมีเวลาคิดเกี่ยวกับงานนั้น การลดความซับซ้อนเหล่านี้รวมถึงพอยน์เตอร์อัจฉริยะ ฟังก์ชันแลมบ์ดา และอื่นๆ อีกมากมายที่ปรากฏในภาษา ขณะเดียวกันก็ไม่ปฏิเสธว่าตอนนี้เราต้องเรียนมากขึ้น แต่จะเรียนอะไรผิดล่ะ? หรือไม่มีการเปลี่ยนแปลงในภาษายอดนิยมอื่น ๆ ที่ต้องเรียนรู้?

นอกจากนี้ฉันไม่สงสัยเลยว่าจะมีคนเย่อหยิ่งที่สามารถตอบได้ว่า: “คุณไม่อยากคิดเหรอ? จากนั้นไปเขียนใน PHP” ฉันไม่อยากตอบคนแบบนี้ด้วยซ้ำ ฉันจะยกตัวอย่างจากความเป็นจริงในเกม: ในส่วนแรกของ Starcraft เมื่อมีการสร้างคนงานใหม่ขึ้นมาในอาคาร เพื่อที่เขาจะเริ่มขุดแร่ (หรือก๊าซ) เขาจะต้องถูกส่งไปที่นั่นด้วยตนเอง ยิ่งไปกว่านั้น แร่ธาตุแต่ละห่อยังมีขีดจำกัด เมื่อถึงขีดจำกัดแล้ว คนงานที่เพิ่มขึ้นก็ไม่มีประโยชน์ และพวกมันยังสามารถรบกวนซึ่งกันและกัน ส่งผลให้การผลิตแย่ลง สิ่งนี้มีการเปลี่ยนแปลงใน Starcraft 2: คนงานจะเริ่มขุดแร่ (หรือก๊าซ) โดยอัตโนมัติ และยังระบุจำนวนคนงานที่กำลังขุดอยู่และขีดจำกัดของเงินฝากนี้คือเท่าใด สิ่งนี้ทำให้การโต้ตอบของผู้เล่นกับฐานง่ายขึ้นอย่างมาก ทำให้เขามุ่งเน้นไปที่แง่มุมที่สำคัญกว่าของเกม: การสร้างฐาน การสะสมกองกำลัง และการทำลายศัตรู ดูเหมือนว่านี่เป็นเพียงนวัตกรรมที่ยอดเยี่ยม แต่สิ่งที่เริ่มต้นบนอินเทอร์เน็ต! ผู้คน (พวกเขาเป็นใคร?) เริ่มกรีดร้องว่าเกมนี้ "พัง" และ "พวกเขาฆ่าสตาร์คราฟต์" แน่นอนว่าข้อความดังกล่าวอาจมาจาก "ผู้รักษาความรู้ที่เป็นความลับ" และ "ผู้เชี่ยวชาญใน APM ระดับสูง" ที่ชอบอยู่ในคลับ "ชนชั้นสูง" เท่านั้น

กลับมาที่หัวข้อของเรา ยิ่งฉันต้องคิดถึงวิธีเขียนโค้ดน้อยลงเท่าไหร่ ฉันก็ยิ่งมีเวลาคิดในการแก้ปัญหาเฉพาะหน้ามากขึ้นเท่านั้น การคิดว่าฉันควรใช้วิธีใด - PPSC หรือ PPZ - ไม่ได้ทำให้ฉันเข้าใกล้การแก้ปัญหาเพียงเล็กน้อยเท่านั้น ดังนั้นฉันจึงปฏิเสธที่จะคิดเกี่ยวกับสิ่งเหล่านั้นและเลือกหนึ่งตัวเลือก: ผ่านการอ้างอิงถึงค่าคงที่ ทำไม เพราะไม่เห็นข้อดีของ PPP ในกรณีทั่วไป และกรณีพิเศษต้องพิจารณาแยกกัน

เป็นกรณีพิเศษ เพียงแต่สังเกตว่าในบางวิธี PPSC กลายเป็นคอขวด และด้วยการเปลี่ยนระบบส่งกำลังเป็น PPZ เราจะได้รับประสิทธิภาพที่เพิ่มขึ้นอย่างมาก ฉันไม่ลังเลเลยที่จะใช้ พีพีแซด. แต่โดยค่าเริ่มต้น ฉันจะใช้ PPSC ทั้งในฟังก์ชันปกติและในคอนสตรัคเตอร์ และถ้าเป็นไปได้ ฉันจะส่งเสริมวิธีการเฉพาะนี้ทุกครั้งที่เป็นไปได้ ทำไม เพราะฉันคิดว่าแนวทางปฏิบัติในการส่งเสริม PPP นั้นเลวร้ายเนื่องจากโปรแกรมเมอร์ส่วนใหญ่ไม่มีความรู้มากนัก (โดยหลักการแล้วหรือยังไม่ได้เข้าสู่การเปลี่ยนแปลงของสิ่งต่าง ๆ เลย) และพวกเขาก็ทำตามคำแนะนำ นอกจากนี้ หากมีคำแนะนำหลายข้อที่ขัดแย้งกัน พวกเขาก็จะเลือกคำแนะนำที่ง่ายกว่า และสิ่งนี้นำไปสู่การมองโลกในแง่ร้ายในโค้ดเพียงเพราะมีคนได้ยินอะไรบางอย่างจากที่ไหนสักแห่ง โอ้ ใช่แล้ว คนนี้สามารถให้ลิงก์ไปยังบทความของ Abrahams เพื่อพิสูจน์ว่าเขาพูดถูก จากนั้นคุณนั่งอ่านโค้ดแล้วคิดว่า: ความจริงที่ว่าพารามิเตอร์ถูกส่งผ่านด้วยค่าที่นี่เพราะโปรแกรมเมอร์ที่เขียนสิ่งนี้มาจาก Java เพียงแค่อ่านบทความ "ฉลาด" จำนวนมากหรือมีความจำเป็นจริงๆ ข้อกำหนดทางเทคนิค?

PPSC อ่านง่ายกว่ามาก: บุคคลนั้นรู้อย่างชัดเจนถึง "รูปแบบที่ดี" ของ C ++ และเราก็เดินหน้าต่อไป - การจ้องมองจะไม่อ้อยอิ่งอยู่ การฝึกใช้ PPSC ได้รับการสอนให้กับโปรแกรมเมอร์ C ++ มาหลายปีแล้ว เหตุใดจึงละทิ้งมัน? สิ่งนี้ทำให้ฉันได้ข้อสรุปอีกประการหนึ่ง: หากอินเทอร์เฟซของวิธีการใช้ PPP ก็ควรมีความคิดเห็นด้วยว่าทำไมจึงเป็นเช่นนั้น ในกรณีอื่น ๆ ต้องใช้ PPSC แน่นอนว่ามีประเภทข้อยกเว้น แต่ฉันไม่ได้พูดถึงพวกเขาที่นี่เพียงเพราะมันโดยนัย: string_view , Initializer_list , ตัววนซ้ำต่างๆ ฯลฯ แต่สิ่งเหล่านี้เป็นข้อยกเว้น รายการที่สามารถขยายได้ขึ้นอยู่กับประเภทที่ใช้ในโครงการ แต่สาระสำคัญยังคงเหมือนเดิมตั้งแต่ C++98: โดยค่าเริ่มต้น เราจะใช้ PPCS เสมอ

สำหรับ std::string มักจะไม่มีความแตกต่างกับสตริงเล็กๆ เราจะพูดถึงเรื่องนี้ในภายหลัง

ฉันขอโทษล่วงหน้าสำหรับคำอธิบายประกอบที่อวดรู้เกี่ยวกับ "จุดวาง" แต่เราต้องล่อให้คุณเข้าสู่บทความ)) ในส่วนของฉัน ฉันจะพยายามให้แน่ใจว่านามธรรมยังคงตรงตามความคาดหวังของคุณ

สั้นๆ ว่าเรากำลังพูดถึงเรื่องอะไร

ทุกคนรู้เรื่องนี้อยู่แล้ว แต่ในตอนแรกฉันจะเตือนคุณว่าพารามิเตอร์ของวิธีการสามารถส่งผ่านใน 1C ได้อย่างไร สามารถส่งผ่าน "โดยการอ้างอิง" หรือ "ตามค่า" ในกรณีแรก เราจะส่งผ่านไปยังเมธอดด้วยค่าเดียวกันกับ ณ จุดที่โทร และในกรณีที่สอง เราจะส่งสำเนาของมัน

ตามค่าเริ่มต้นใน 1C อาร์กิวเมนต์จะถูกส่งผ่านโดยการอ้างอิง และการเปลี่ยนแปลงพารามิเตอร์ภายในวิธีการจะมองเห็นได้จากภายนอกวิธีการ ในที่นี้ การทำความเข้าใจคำถามเพิ่มเติมนั้นขึ้นอยู่กับสิ่งที่คุณเข้าใจอย่างชัดเจนจากคำว่า "การเปลี่ยนแปลงพารามิเตอร์" ดังนั้น นี่หมายถึงการมอบหมายงานใหม่และไม่มีอะไรเพิ่มเติม ยิ่งไปกว่านั้น การมอบหมายสามารถเป็นนัยได้ เช่น การเรียกใช้เมธอดแพลตฟอร์มที่ส่งคืนบางสิ่งในพารามิเตอร์เอาต์พุต

แต่ถ้าเราไม่ต้องการให้พารามิเตอร์ของเราถูกส่งผ่านโดยการอ้างอิง เราก็สามารถระบุคำหลักก่อนพารามิเตอร์ได้ ความหมาย

ขั้นตอน ByValue (พารามิเตอร์ค่า) พารามิเตอร์ = 2; พารามิเตอร์ EndProcedure = 1; ByValue (พารามิเตอร์); รายงาน (พารามิเตอร์); // จะพิมพ์ 1

ทุกอย่างทำงานตามที่สัญญาไว้ - การเปลี่ยน (หรือค่อนข้าง "แทนที่") ค่าพารามิเตอร์จะไม่เปลี่ยนค่านอกวิธีการ

แล้วเรื่องตลกล่ะ?

ช่วงเวลาที่น่าสนใจเริ่มต้นเมื่อเราเริ่มส่งประเภทที่ไม่ใช่แบบดั้งเดิม (สตริง ตัวเลข วันที่ ฯลฯ) เป็นพารามิเตอร์ แต่เป็นวัตถุ นี่คือจุดที่แนวคิดเช่นสำเนาอ็อบเจ็กต์ "ตื้น" และ "ลึก" เข้ามามีบทบาท เช่นเดียวกับพอยน์เตอร์ (ไม่ใช่ในรูปแบบ C ++ แต่เป็นการจัดการแบบนามธรรม)

เมื่อส่งผ่านวัตถุ (เช่น ตารางค่า) โดยการอ้างอิง เราจะส่งค่าตัวชี้เอง (ตัวจับบางตัว) ซึ่ง "เก็บ" วัตถุไว้ในหน่วยความจำของแพลตฟอร์ม เมื่อส่งผ่านค่า แพลตฟอร์มจะทำสำเนาของตัวชี้นี้

กล่าวอีกนัยหนึ่ง หากส่งวัตถุโดยการอ้างอิงในวิธีที่เรากำหนดค่า “อาร์เรย์” ให้กับพารามิเตอร์ จากนั้น ณ จุดที่เรียกใช้ เราจะได้รับอาร์เรย์ การกำหนดค่าที่ส่งผ่านโดยการอ้างอิงใหม่สามารถมองเห็นได้จากตำแหน่งการโทร

ขั้นตอน ProcessValue (พารามิเตอร์) พารามิเตอร์ = อาร์เรย์ใหม่; ตาราง EndProcedure = ValueTable ใหม่; ค่ากระบวนการ (ตาราง); รายงาน (ประเภทค่า (ตาราง)); // จะส่งออกอาร์เรย์

หากเราส่งวัตถุตามค่า เมื่อถึงจุดที่มีการเรียกตารางค่าของเราจะไม่สูญหาย

เนื้อหาของวัตถุและสถานะ

เมื่อส่งผ่านค่า จะไม่มีการคัดลอกวัตถุทั้งหมด แต่จะคัดลอกเฉพาะตัวชี้เท่านั้น อินสแตนซ์ของวัตถุยังคงเหมือนเดิม ไม่สำคัญว่าคุณจะส่งวัตถุอย่างไร โดยการอ้างอิงหรือตามค่า การล้างตารางค่าจะล้างตารางเอง การทำความสะอาดนี้จะปรากฏให้เห็นทุกที่ เพราะ... มีเพียงวัตถุเดียวเท่านั้นและไม่สำคัญว่าจะถูกส่งผ่านไปยังวิธีการอย่างไร

ขั้นตอน ProcessValue (พารามิเตอร์) พารามิเตอร์ Clear(); ตาราง EndProcedure = ValueTable ใหม่; ตาราง.เพิ่ม(); ค่ากระบวนการ (ตาราง); รายงาน(ตาราง.ปริมาณ()); // จะส่งออกเป็น 0

เมื่อส่งวัตถุไปยังเมธอด แพลตฟอร์มจะทำงานด้วยพอยน์เตอร์ (มีเงื่อนไข ไม่ใช่อะนาล็อกโดยตรงจาก C++) หากวัตถุถูกส่งผ่านโดยการอ้างอิง เซลล์หน่วยความจำของเครื่องเสมือน 1C ที่วัตถุนั้นอยู่นั้นสามารถถูกเขียนทับโดยวัตถุอื่นได้ หากวัตถุถูกส่งผ่านด้วยค่า ตัวชี้จะถูกคัดลอกและการเขียนทับวัตถุจะไม่ส่งผลให้มีการเขียนทับตำแหน่งหน่วยความจำด้วยวัตถุต้นฉบับ

ขณะเดียวกันก็เกิดการเปลี่ยนแปลงใดๆ สถานะ object (การทำความสะอาด การเพิ่มคุณสมบัติ ฯลฯ) จะเปลี่ยนอ็อบเจ็กต์เอง และไม่เกี่ยวข้องกับวิธีการและตำแหน่งของอ็อบเจ็กต์ที่ถูกถ่ายโอนเลย สถานะของอินสแตนซ์ออบเจ็กต์มีการเปลี่ยนแปลง อาจมี "การอ้างอิง" และ "ค่า" มากมาย แต่อินสแตนซ์จะเหมือนเดิมเสมอ โดยการส่งวัตถุไปยังวิธีการ เราจะไม่สร้างสำเนาของวัตถุทั้งหมด

และสิ่งนี้ก็เป็นจริงเสมอ ยกเว้น...

การโต้ตอบระหว่างไคลเอนต์และเซิร์ฟเวอร์

แพลตฟอร์มนี้ใช้การเรียกเซิร์ฟเวอร์อย่างโปร่งใสมาก เราเพียงเรียกเมธอด และภายใต้ประทุนนั้น แพลตฟอร์มจะซีเรียลไลซ์พารามิเตอร์ทั้งหมดของเมธอด (กลายเป็นสตริง) แล้วส่งพารามิเตอร์เหล่านั้นไปยังเซิร์ฟเวอร์ จากนั้นส่งคืนพารามิเตอร์เอาต์พุตกลับไปยังไคลเอนต์ โดยที่พารามิเตอร์เหล่านั้นจะถูกดีซีเรียลไลซ์และใช้งานอยู่ หากพวกเขาไม่เคยไปที่เซิร์ฟเวอร์ใด ๆ

ดังที่คุณทราบ ไม่ใช่ว่าออบเจ็กต์แพลตฟอร์มทั้งหมดจะสามารถซีเรียลไลซ์ได้ นี่คือจุดที่ข้อจำกัดเพิ่มมากขึ้น: ไม่ใช่ทุกอ็อบเจ็กต์ที่สามารถส่งผ่านไปยังเมธอดเซิร์ฟเวอร์จากไคลเอนต์ได้ หากคุณส่งวัตถุที่ไม่สามารถทำให้เป็นอนุกรมได้ แพลตฟอร์มจะเริ่มใช้คำที่ไม่เหมาะสม

  • การประกาศเจตนาของโปรแกรมเมอร์อย่างชัดเจน เมื่อดูที่ลายเซ็นวิธีการ คุณสามารถบอกได้อย่างชัดเจนว่าพารามิเตอร์ใดเป็นอินพุตและเอาต์พุตใด รหัสนี้ง่ายต่อการอ่านและบำรุงรักษา
  • เพื่อให้การเปลี่ยนแปลงในพารามิเตอร์ "โดยการอ้างอิง" บนเซิร์ฟเวอร์สามารถมองเห็นได้ที่จุดเรียกบนไคลเอนต์ หน้าแพลตฟอร์มเองจำเป็นต้องส่งคืนพารามิเตอร์ที่ส่งไปยังเซิร์ฟเวอร์ผ่านลิงก์ไปยังไคลเอนต์ เพื่อให้แน่ใจว่าพฤติกรรมที่อธิบายไว้ตอนต้นของบทความ หากไม่จำเป็นต้องส่งคืนพารามิเตอร์ จะมีการรับส่งข้อมูลมากเกินไป เพื่อเพิ่มประสิทธิภาพการแลกเปลี่ยนข้อมูล พารามิเตอร์ที่มีค่าที่เราไม่ต้องการในเอาต์พุตควรทำเครื่องหมายด้วยคำว่า Value

ประเด็นที่สองน่าสังเกตที่นี่ เพื่อเพิ่มประสิทธิภาพการรับส่งข้อมูล แพลตฟอร์มจะไม่ส่งคืนค่าพารามิเตอร์ไปยังไคลเอนต์หากพารามิเตอร์ถูกทำเครื่องหมายด้วยคำว่า Value ทั้งหมดนี้ยอดเยี่ยม แต่มันนำไปสู่ผลลัพธ์ที่น่าสนใจ

ดังที่ฉันได้กล่าวไปแล้ว เมื่อวัตถุถูกถ่ายโอนไปยังเซิร์ฟเวอร์ การทำให้เป็นอนุกรมจะเกิดขึ้น เช่น ทำการคัดลอกวัตถุ "เชิงลึก" แล้วถ้ามีคำพูด. ความหมายวัตถุจะไม่เดินทางจากเซิร์ฟเวอร์กลับไปยังไคลเอนต์ เราเพิ่มข้อเท็จจริงทั้งสองนี้และรับสิ่งต่อไปนี้:

&OnServerProcedureByLink(พารามิเตอร์) พารามิเตอร์Clear(); EndProcedure &OnServerProcedureByValue (พารามิเตอร์ค่า) พารามิเตอร์ Clear(); EndProcedure &ขั้นตอน OnClient ByValueClient (พารามิเตอร์ค่า) พารามิเตอร์ Clear(); EndProcedure &OnClient ขั้นตอน CheckValue() List1= New ListValues; List1.Add("สวัสดี"); List2 = List1.คัดลอก(); List3 = List1.คัดลอก(); // วัตถุถูกคัดลอกอย่างสมบูรณ์ // ถ่ายโอนไปยังเซิร์ฟเวอร์แล้วส่งคืน // การล้างรายการจะมองเห็นได้ที่จุดโทร ByRef(List1); // วัตถุถูกคัดลอกอย่างสมบูรณ์ // ถ่ายโอนไปยังเซิร์ฟเวอร์ มันไม่กลับมา // การล้างรายการไม่สามารถมองเห็นได้ ณ จุดที่เรียก ByValue(List2); // คัดลอกเฉพาะตัวชี้วัตถุเท่านั้น // การล้างรายการสามารถมองเห็นได้ ณ จุดที่เรียกใช้ ByValueClient (List3); รายงาน (List1.Quantity()); รายงาน (List2.Quantity()); รายงาน (List3.Quantity()); สิ้นสุดขั้นตอน

สรุป

โดยสรุปสามารถสรุปได้ดังนี้:

  • การส่งผ่านโดยการอ้างอิงทำให้คุณสามารถ "เขียนทับ" วัตถุด้วยวัตถุที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
  • การส่งผ่านค่าไม่อนุญาตให้คุณ "เขียนทับ" วัตถุ แต่จะมองเห็นการเปลี่ยนแปลงในสถานะภายในของวัตถุได้เนื่องจาก เรากำลังทำงานกับอินสแตนซ์วัตถุเดียวกัน
  • เมื่อทำการเรียกเซิร์ฟเวอร์ งานจะเสร็จสิ้นกับอินสแตนซ์ที่แตกต่างกันของอ็อบเจ็กต์ เนื่องจาก ทำการคัดลอกแบบลึก คำสำคัญ ความหมายจะป้องกันไม่ให้อินสแตนซ์เซิร์ฟเวอร์ถูกคัดลอกกลับไปยังอินสแตนซ์ไคลเอนต์ และการเปลี่ยนแปลงสถานะภายในของออบเจ็กต์บนเซิร์ฟเวอร์จะไม่นำไปสู่การเปลี่ยนแปลงที่คล้ายกันบนไคลเอนต์

ฉันหวังว่ารายการกฎง่ายๆ นี้จะช่วยให้คุณแก้ไขข้อพิพาทกับเพื่อนร่วมงานเกี่ยวกับการส่งพารามิเตอร์ "ตามค่า" และ "โดยการอ้างอิง" ได้ง่ายขึ้นสำหรับคุณ