จุดประสงค์ของฟังก์ชั่นเสมือนส่วนตัวที่บริสุทธิ์คืออะไร?


139

ฉันเจอรหัสต่อไปนี้ในไฟล์ส่วนหัว:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

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

คำตอบ:


209

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

ดังนั้นเพื่อกำจัดความสับสนก่อน: ใช่ฟังก์ชั่นเสมือนส่วนตัวสามารถถูกแทนที่ในชั้นเรียนที่ได้รับ วิธีการเรียนที่ได้รับไม่สามารถเรียกฟังก์ชั่นเสมือนจากชั้นฐาน แต่พวกเขาสามารถให้การใช้งานของพวกเขาเองสำหรับพวกเขา อ้างอิงจาก Herb Sutter การมีส่วนต่อประสานแบบ non-virtual สาธารณะในคลาสพื้นฐานและการใช้งานส่วนตัวที่สามารถปรับแต่งได้ในคลาสที่ได้รับอนุญาตให้ "แยกสเปคของอินเตอร์เฟสได้จากสเปคของพฤติกรรมที่ปรับใช้งานได้" คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ในบทความ"Virtuality" ของเขา

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

"Public Non-Virtuals ที่โอเวอร์โหลดที่มีการป้องกันการโทรเสมือนที่ไม่โอเวอร์โหลด"

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

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

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

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

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

หากคุณลืมใส่การประกาศโดยใช้ในคลาสที่ได้รับ (หรือเพื่อกำหนดโอเวอร์โหลดที่สองอีกครั้ง) คุณอาจประสบปัญหาในสถานการณ์ด้านล่าง

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

หากคุณไม่ได้ป้องกันการซ่อนEngineสมาชิกข้อความ:

myV8->SetState(5, true);

จะเรียกvoid SetState( int var, int val )จากชั้นเรียนมาแปลงไปtrueint

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

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

ทำไมฟังก์ชั่นเสมือนต้องเป็นส่วนตัว? เป็นสาธารณะหรือไม่?
รวย

ฉันสงสัยว่าแนวทางที่กำหนดโดย Herb Sutter ในบทความ "Virtuality" ของเขายังคงมีอยู่ในปัจจุบันหรือไม่?
nurabha

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

43

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

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

เพียวเสมือน - เพียงแค่บังคับคลาสที่ได้รับเพื่อใช้งาน

แก้ไข : เพิ่มเติมเกี่ยวกับเรื่องนี้: Wikipedia :: NVI-idiom


17

ทีนี้สำหรับอันนี้จะอนุญาตให้คลาสที่ได้รับเพื่อใช้ฟังก์ชันที่คลาสฐาน (ที่มีการประกาศฟังก์ชันเสมือนบริสุทธิ์) สามารถเรียกได้


5
ที่คลาสฐานเท่านั้นที่สามารถเรียกได้!
underscore_d

4

แก้ไข: ชี้แจงงบเกี่ยวกับความสามารถในการแทนที่และความสามารถในการเข้าถึง / เรียกใช้

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

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

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


ฉันเห็น แต่ถ้าคลาสที่ได้รับมีการเข้าถึงแบบใดก็ตามทำไมต้องทำให้พวกเขาเป็นส่วนตัว?
BeeBand

@BeeBand: ผู้ใช้จะสามารถเข้าถึงวิธีการแทนที่คลาสเสมือนสาธารณะที่ได้รับ แต่จะไม่สามารถเข้าถึงคลาสพื้นฐานได้ ผู้เขียนคลาสที่ได้รับในกรณีนี้สามารถทำให้เมธอดเสมือนแทนที่ไพรเวตได้เช่นกัน อันที่จริงฉันจะทำการเปลี่ยนแปลงในโค้ดตัวอย่างด้านบนเพื่อเน้นว่า ไม่ว่าจะด้วยวิธีใดพวกเขาสามารถสืบทอดต่อสาธารณะและแทนที่เมธอดเสมือนคลาสฐานส่วนตัว แต่พวกเขาต้องการเข้าถึงวิธีเสมือนคลาสคลาสที่ได้รับมาเท่านั้น โปรดทราบว่าฉันกำลังทำให้ความแตกต่าง beteween แทนที่และการเข้าถึง / ขอร้อง
โมฆะ

เพราะคุณผิด การมองเห็นการสืบทอดระหว่างคลาสEngineและDerivedEngineไม่มีส่วนเกี่ยวข้องกับสิ่งที่DerivedEngineสามารถหรือไม่สามารถแทนที่ (หรือการเข้าถึงสำหรับเรื่องนั้น)
wilhelmtell

@wilhelmtell: ถอนหายใจ คุณถูกต้องแน่นอน ฉันจะอัปเดตคำตอบของฉันตาม
ว่างเปล่า

3

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

คำอธิบายสั้น ๆ สามารถพบได้ของDevX.com


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

บทความที่น่าสนใจสามารถพบได้เกี่ยวกับVirtuality


2
@ สุภาพบุรุษ ... hmmm เลื่อนลงไปที่ความคิดเห็นของ Colin D Bennett ดูเหมือนว่าเขาจะคิดว่า "ฟังก์ชั่นเสมือนส่วนตัวสามารถถูกแทนที่โดยคลาสที่ได้รับ แต่สามารถถูกเรียกจากภายในคลาสพื้นฐานเท่านั้น" @Michael Goldshteyn ก็คิดเช่นกันเช่นกัน
BeeBand

ฉันเดาว่าคุณลืมหลักการที่ไม่สามารถมองเห็นชั้นเรียนส่วนตัวได้ นั่นคือกฎ OOP และใช้กับทุกภาษาที่เป็น OOP เพื่อให้คลาสที่ได้รับมาใช้งานเมธอดเสมือนคลาสพื้นฐานของคลาสนั้นจะต้องเป็นfriendคลาสพื้นฐาน Qt ใช้แนวทางเดียวกันเมื่อใช้โมเดลเอกสาร XML DOM
Buhake Sindi

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

@ สุภาพบุรุษ: @wilhelmtell ชี้ให้เห็นข้อผิดพลาดในคำตอบ / ความคิดเห็นของฉัน การอ้างสิทธิ์ของฉันเกี่ยวกับการสืบทอดที่มีผลต่อการเข้าถึงคลาสที่ได้รับของเมธอดคลาสพื้นฐานปิด ฉันได้ลบความคิดเห็นที่ละเมิดไปยังคำตอบของคุณ
ว่างเปล่า

@ เป็นโมฆะฉันเห็นว่าคลาสที่ได้รับสามารถแทนที่เมธอดเสมือนคลาสพื้นฐานของคลาสนั้น แต่ไม่สามารถใช้งานได้ ดังนั้นนี่คือรูปแบบของเมธอดเทมเพลต
Buhake Sindi

0

TL; DR คำตอบ:

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

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

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