เหตุใดเราจึงใส่ฟังก์ชันสมาชิกส่วนตัวไว้ในส่วนหัว


16

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

ทำไมเราต้องใส่สมาชิกส่วนบุคคลไว้ในหัว?

แต่มีเหตุผลใดที่จะประกาศฟังก์ชั่นส่วนตัวในคำจำกัดความของชั้นเรียน?

ทางเลือกจะเป็นสำนวน pimpl เป็นหลัก แต่ไม่มีทิศทางฟุ่มเฟือย

คุณสมบัติภาษานี้เป็นมากกว่าข้อผิดพลาดในอดีตหรือไม่?

คำตอบ:


11

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

อาจดูเหมือนว่านี่เป็นเหมือน "การวางเกวียนก่อนม้า" เนื่องจากตัวเลือกการใช้งานคอมไพเลอร์ไม่ควรส่งผลกระทบต่อข้อกำหนดภาษา อย่างไรก็ตามในความเป็นจริงภาษา C ++ เองได้รับการพัฒนาในเวลาเดียวกันกับการใช้งาน ( Cfront ) ซึ่งใช้ vtables


4
"ตัวเลือกการใช้งานไม่ควรส่งผลกระทบต่อภาษา" เกือบตรงกันข้ามกับ C ++ มนต์ ข้อกำหนดไม่ได้บังคับใช้การดำเนินการใด ๆ โดยเฉพาะ แต่มีกฎจำนวนมากที่ออกแบบมาโดยเฉพาะเพื่อตอบสนองความต้องการของตัวเลือกการใช้งานที่มีประสิทธิภาพเป็นพิเศษ
Ben Voigt

ฟังดูน่าเชื่อถือมาก มีแหล่งที่มาเกี่ยวกับเรื่องนี้?
Praxeolitic

@Praxeolitic: คุณสามารถอ่านข้อมูลเกี่ยวกับตารางวิธีเสมือน
Greg Hewgill

1
@Praxeolitic - ค้นหาสำเนาเก่าของ 'The Annotated C ++ Refernce Manual' ( stroustrup.com/arm.html ) - ผู้เขียนพูดคุยเกี่ยวกับรายละเอียดการติดตั้งที่หลากหลายและวิธีการกำหนดรูปแบบภาษา
Michael Kohne

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

13

หากคุณอนุญาตให้มีการเพิ่มวิธีการในชั้นเรียนนอกคำจำกัดความของพวกเขาก็สามารถเพิ่มได้ทุกที่ทุกคนในไฟล์ใด ๆ

นั่นจะทำให้ลูกค้าทุกคนเข้าถึงข้อมูลส่วนตัวและป้องกันข้อมูลได้เล็กน้อย

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


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

ตัวระบุการเข้าถึงเหล่านี้ไม่ได้พยายามป้องกันการโจมตีเหล่านี้เพราะมันเป็นไปไม่ได้ พวกเขาระบุว่าควรใช้คลาสอย่างไร


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

1
แน่นอน แต่รูปแบบการรวบรวมไม่ได้ให้วิธีการที่เหมาะสมในการหยุดรหัสลูกค้าที่ซ่อนคอนเทนเนอร์ที่ซ่อนอยู่หรืออินเทอร์เฟซที่แตกต่างที่มองเห็นได้ด้วยตัวเสริมพิเศษ
ไร้ประโยชน์

นั่นเป็นประเด็นที่ดี แต่นี่ไม่จริงสำหรับคำจำกัดความของฟังก์ชั่นสมาชิกทั้งหมดที่ไม่ได้อยู่ในส่วนหัวใช่ไหม?
Praxeolitic

1
ฟังก์ชั่นสมาชิกทั้งหมดจะประกาศในชั้นเรียนว่า ประเด็นก็คือคุณไม่สามารถเพิ่มฟังก์ชั่นสมาชิกใหม่ที่ไม่ได้ประกาศอย่างน้อยในนิยามคลาส (และกฎหนึ่งนิยามป้องกันหลายนิยาม)
ไร้ประโยชน์

ถ้าอย่างนั้นจะมีกฎของฟังก์ชั่นส่วนตัวหนึ่งคอนเทนเนอร์
Praxeolitic

8

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

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

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

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

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

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


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

1
@ NicolBolas แน่นอน นั่นอาจเป็นถ้อยคำที่ไม่ค่อยดีนักในส่วนของฉัน ฉันหมายความว่าคำตอบนั้นจะอธิบายการมองเห็นของวิธีการส่วนตัวเท่านั้นซึ่งเป็นผลมาจากกฎ (ที่ถูกต้องมาก) ที่ครอบคลุมหลาย ๆ สิ่งแทนที่จะให้เหตุผลเกี่ยวกับการมองเห็นวิธีการส่วนตัวโดยเฉพาะ แน่นอนคำตอบที่แท้จริงคือการผสมผสานของ Useless's, Gnasher's และ Mine ฉันแค่ต้องการให้มุมมองอื่นที่ไม่ได้อยู่ที่นี่
underscore_d

4

มีสองเหตุผลในการทำเช่นนี้

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

ความรัดกุม

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

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

มีข้อได้เปรียบที่สำคัญอีกประการหนึ่งซึ่งก็คือ ...

ฟังก์ชั่นแบบอินไลน์

ฟังก์ชั่นส่วนตัวอาจจะสามารถ inline และจำเป็นต้องให้มันอยู่ในส่วนหัว พิจารณาสิ่งนี้:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

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


ฟังก์ชั่นทั้งหมดที่กำหนดไว้ภายในร่างกายของชั้นเรียนมีการทำเครื่องหมายโดยอัตโนมัติinlineไม่มีเหตุผลที่จะใช้คำหลักที่มี
Ben Voigt

@ BenVoigt ดังนั้นจึงเป็น ฉันไม่ได้ตระหนักถึงสิ่งนั้นและฉันใช้ C ++ พาร์ทไทม์มาระยะหนึ่งแล้ว ภาษานั้นไม่เคยทำให้ฉันประหลาดใจกับนักเก็ตเล็ก ๆ เช่นนั้น

ฉันไม่คิดว่าวิธีการจะต้องอยู่ในส่วนหัวที่จะถูกแทรก มันแค่ต้องอยู่ในหน่วยการคอมไพล์เดียวกัน มีกรณีที่เหมาะสมหรือไม่ที่จะมีหน่วยการคอมไพล์แยกต่างหากสำหรับคลาส?
ซามูเอลแดเนียล

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

เนื้อความของคำถามคือ "มีเหตุผลใดที่จะประกาศฟังก์ชั่นส่วนตัวในคำจำกัดความของคลาส [sic ซึ่งบริบทแสดงให้เห็น OP หมายถึงการประกาศคลาสจริงๆ"] คุณกำลังพูดถึงการกำหนดฟังก์ชั่นส่วนตัว สิ่งนี้ไม่ตอบคำถาม @SamuelDanielson ทุกวันนี้ LTO หมายถึงฟังก์ชั่นสามารถอยู่ที่ใดก็ได้ในโครงการและรักษาโอกาสที่เท่าเทียมกัน สำหรับการแบ่งคลาสออกเป็นหลายหน่วยการแปลกรณีที่ง่ายที่สุดคือคลาสนั้นใหญ่และคุณต้องการแยกความหมายออกเป็นไฟล์ต้นฉบับหลายไฟล์ ฉันแน่ใจว่าชั้นเรียนขนาดใหญ่ดังกล่าวท้อแท้ แต่อย่างไรก็ตาม
underscore_d

2

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

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


จุดที่ยอดเยี่ยมเกี่ยวกับการอินไลน์! ฉันคิดว่านี่เป็นสิ่งที่เกี่ยวข้องโดยเฉพาะอย่างยิ่งสำหรับ stdlib และห้องสมุดอื่น ๆ ที่มีวิธีการแบบอินไลน์ที่กำหนดไว้ (ถ้าไม่มากสำหรับรหัสภายในที่ LTO สามารถทำเกือบทุกอย่างข้ามขอบเขตของ TU)
underscore_d

0

เป็นการอนุญาตให้ฟังก์ชั่นเหล่านั้นเข้าถึงสมาชิกส่วนตัวได้ มิฉะนั้นคุณจะต้องให้friendพวกเขาในส่วนหัวต่อไป

หากฟังก์ชั่นใด ๆ สามารถเข้าถึงสมาชิกส่วนตัวของชั้นเรียนแล้วส่วนตัวจะไม่มีประโยชน์


1
บางทีฉันควรจะชัดเจนมากขึ้นในคำถาม - ฉันหมายถึงทำไมภาษาที่ออกแบบมาในลักษณะนี้? ทำไมต้องเป็นหรือเพราะเหตุใดการออกแบบนี้จึงดี
Praxeolitic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.