วิธีเสมือนส่วนตัวใน C ++


125

ข้อดีของการสร้างเมธอดส่วนตัวเสมือนใน C ++ คืออะไร?

ฉันสังเกตเห็นสิ่งนี้ในโครงการ C ++ โอเพ่นซอร์ส:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
ผมว่าคำถามถอยหลัง เหตุผลในการสร้างสิ่งที่เสมือนมักจะเหมือนกันเสมอ: เพื่อให้คลาสที่ได้รับมาแทนที่มัน ดังนั้นคำถามควรเป็น: ข้อดีของการทำให้วิธีเสมือนเป็นส่วนตัวคืออะไร? คำตอบคือทำให้ทุกอย่างเป็นส่วนตัวตามค่าเริ่มต้น :-)
ShreevatsaR

1
@ShreevatsaR แต่คุณไม่ได้ตอบคำถามของคุณเอง ......
Spencer

@ShreevatsaR ฉันคิดว่าคุณหมายถึงการถอยหลังในทางอื่น: ข้อดีของการทำให้วิธีเสมือนไม่เป็นส่วนตัวคืออะไร?
Peter - คืนสถานะ Monica

คำตอบ:


116

สมุนไพรซัทเทอได้อธิบายอย่างมากมันนี่

แนวทาง # 2: ต้องการให้ฟังก์ชันเสมือนเป็นส่วนตัว

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


อย่างที่คุณคาดเดาจากคำตอบของฉันฉันคิดว่าแนวทางของซัตเทอร์ # 3 ค่อนข้างจะผลักดันแนวปฏิบัติ # 2 ออกไปนอกหน้าต่าง
Spencer

66

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

(ตรงข้ามกับ Herb Sutter ที่อ้างโดย Prasoon Saurav ในคำตอบของเขา C ++ FAQ Lite แนะนำให้ใช้กับระบบเสมือนส่วนตัวส่วนใหญ่เป็นเพราะมักทำให้ผู้คนสับสน)


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

19
อย่างไรก็ตามความสับสนของผู้เชี่ยวชาญยังคงเป็นปัญหา ไม่มีผู้เชี่ยวชาญด้าน C ++ สี่คนที่นั่งข้างๆฉันรู้เรื่องระบบเสมือนส่วนตัว
Newtonx

12

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

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

คุณมีprotectedการประกาศวิธีการเรียนฐาน

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

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

ดังนั้นแนวทางของ Herb Sutter # 3 ... แต่ม้าก็ออกจากโรงนาอยู่ดี

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

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

อัปเดต : ฉันมีข้อเสนอแนะบางอย่างที่อ้างว่าคุณสามารถใช้ฟังก์ชันเสมือน "chain" ได้ด้วยวิธีนี้โดยใช้ฟังก์ชันเสมือนส่วนตัว ถ้าเป็นเช่นนั้นฉันอยากเห็น

คอมไพเลอร์ C ++ ที่ฉันใช้แน่นอนจะไม่ปล่อยให้การใช้งานคลาสที่ได้รับเรียกการใช้งานคลาสพื้นฐานส่วนตัว

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


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

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

3
set_dataในตัวอย่างของคุณต้องการที่จะขยายการทำงานของ คำแนะนำm_data = ndata;และcleanup();อาจถือได้ว่าเป็นค่าคงที่ที่ต้องมีไว้สำหรับการใช้งานทั้งหมด ดังนั้นจึงทำให้cleanup()ไม่ใช่เสมือนและเป็นส่วนตัว เพิ่มการโทรไปยังเมธอดส่วนตัวอื่นที่เสมือนและเป็นส่วนขยายของชั้นเรียนของคุณ ตอนนี้ไม่จำเป็นต้องให้คลาสที่ได้รับมาของคุณเรียกใช้ฐานcleanup()อีกต่อไปรหัสของคุณยังคงสะอาดอยู่และอินเทอร์เฟซของคุณใช้งานไม่ถูกต้องได้ยาก
sigy

2
@sigy นั่นแค่ย้ายเสาประตู คุณต้องมองให้ไกลกว่าตัวอย่างที่เลียนแบบ เมื่อมีลูกหลานเพิ่มเติมที่จำเป็นต้องเรียกทุกคนcleanup()ในห่วงโซ่ข้อโต้แย้งก็แตกสลาย หรือคุณกำลังแนะนำฟังก์ชันเสมือนพิเศษสำหรับลูกหลานแต่ละคนในเครือข่าย? ick แม้แต่ Herb Sutter ยังอนุญาตให้ใช้ฟังก์ชันเสมือนที่ได้รับการป้องกันเป็นช่องโหว่ในแนวทาง # 3 ของเขา อย่างไรก็ตามหากไม่มีรหัสจริงคุณจะไม่มีวันทำให้ฉันเชื่อได้
Spencer

2
แล้วมาเห็นด้วยกับไม่เห็นด้วย;)
sigy

9

ฉันพบแนวคิดนี้เป็นครั้งแรกในขณะที่อ่าน 'Effective C ++' ของ Scott Meyers รายการที่ 35: พิจารณาทางเลือกอื่นสำหรับฟังก์ชันเสมือน ฉันต้องการอ้างอิง Scott Mayers สำหรับคนอื่น ๆ ที่อาจสนใจ

เป็นส่วนหนึ่งของTemplate Method Pattern ผ่านสำนวน Non-Virtual Interface : วิธีการเผชิญหน้ากับสาธารณะไม่ใช่เสมือน แต่จะรวมการเรียกเมธอดเสมือนซึ่งเป็นแบบส่วนตัว จากนั้นคลาสพื้นฐานสามารถรันตรรกะก่อนและหลังการเรียกฟังก์ชันเสมือนส่วนตัว:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

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

  • ทำให้ฟังก์ชั่นเสมือนจริงทำไมprivate? เหตุผลที่ดีที่สุดคือเราได้จัดเตรียมpublicวิธีการเผชิญหน้าไว้แล้ว
  • ทำไมไม่ทำอย่างนั้นprotectedเพื่อที่ฉันจะได้ใช้วิธีนี้กับสิ่งที่น่าสนใจอื่น ฉันคิดว่ามันจะขึ้นอยู่กับการออกแบบของคุณเสมอและคุณเชื่อว่าคลาสพื้นฐานนั้นเหมาะสมกับฉันอย่างไรฉันขอยืนยันว่าผู้สร้างคลาสที่ได้รับควรมุ่งเน้นไปที่การใช้ตรรกะที่จำเป็น อย่างอื่นได้รับการดูแลแล้ว นอกจากนี้ยังมีเรื่องของการห่อหุ้ม

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


3

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

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

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

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