คีย์เวิร์ด“ เสมือน” C ++ สำหรับฟังก์ชันในคลาสที่ได้รับ จำเป็นหรือไม่


221

ด้วยคำจำกัดความของโครงสร้างรับด้านล่าง ...

struct A {
    virtual void hello() = 0;
};

วิธีการ # 1:

struct B : public A {
    virtual void hello() { ... }
};

วิธีการ # 2:

struct B : public A {
    void hello() { ... }
};

มีความแตกต่างระหว่างสองวิธีในการแทนที่ฟังก์ชัน hello หรือไม่?


65
ใน C ++ 11 คุณสามารถเขียน "void hello () แทนที่ {}" เพื่อประกาศอย่างชัดเจนว่าคุณกำลังเอาชนะวิธีเสมือน คอมไพเลอร์จะล้มเหลวหากไม่มีเมธอดเสมือนพื้นฐานและมีความสามารถในการอ่านเช่นเดียวกับการวาง "เสมือน" ในคลาสที่สืบทอด
ShadowChaser

ที่จริงแล้วใน c ++ 11 ของ gcc การเขียนเป็นโมฆะ hello () แทนที่ {} ในคลาสที่ได้รับนั้นดีเพราะคลาสพื้นฐานได้ระบุว่าเมธอด hello () เป็นเสมือนจริง กล่าวอีกนัยหนึ่งการใช้คำว่า virtual ในคลาสที่ได้รับนั้นไม่จำเป็น / จำเป็นสำหรับ gcc / g ++ อย่างไรก็ตาม (ฉันใช้ gcc รุ่น 4.9.2 ใน RPi 3) แต่ก็เป็นวิธีที่ดีที่จะรวมคำหลักเสมือนในวิธีการเรียนที่ได้รับแล้ว
จะ

คำตอบ:


183

พวกเขาเหมือนกันทุกประการ ไม่มีความแตกต่างระหว่างพวกเขานอกเหนือจากที่วิธีแรกต้องพิมพ์เพิ่มเติมและอาจชัดเจน


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

5
ฉันยังจะเพิ่มการทำเครื่องหมายอย่างชัดเจนว่าเป็นเสมือนจริงจะช่วยเตือนให้คุณสร้าง destructor เสมือนจริงเช่นกัน
lfalin

1
มีเพียงกล่าวถึงเช่นเดียวกับที่ใช้กับdestructor เสมือนจริง
Atul

6
@SergeyTachenov ตามความเห็นของcliffordต่อคำตอบของเขาตัวอย่างของคอมไพเลอร์ดังกล่าวคือ armcc
Ruslan

4
@Rasmi คู่มือพกพาใหม่อยู่ที่นี่แต่ตอนนี้แนะนำให้ใช้overrideคำสำคัญ
Sergei Tachenov

83

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

จากมุมมองของโวหารอย่างหมดจดรวมถึงvirtualคำหลักที่ชัดเจนว่า 'โฆษณา' ข้อเท็จจริงกับผู้ใช้ว่าฟังก์ชันนั้นเป็นเสมือนจริง สิ่งนี้จะมีความสำคัญสำหรับทุกคนที่มีการจัดหมวดหมู่ย่อย B เพิ่มเติมโดยไม่ต้องตรวจสอบคำจำกัดความของ A สำหรับลำดับชั้นลึกนี่เป็นสิ่งสำคัญอย่างยิ่ง


12
คอมไพเลอร์ตัวนี้คืออะไร?
James McNellis

35
@James: armcc (คอมไพเลอร์ ARM สำหรับอุปกรณ์ ARM)
Clifford

55

virtualคำหลักที่ไม่จำเป็นในชั้นเรียนมา นี่คือเอกสารประกอบที่สนับสนุนจาก C ++ Draft Standard (N3337) (เหมืองที่เน้น):

10.3 ฟังก์ชั่นเสมือนจริง

2 หากฟังก์ชันสมาชิกเสมือนvfถูกประกาศในคลาสBaseและในคลาสที่Derivedได้รับโดยตรงหรือโดยอ้อมจากBaseฟังก์ชันสมาชิกที่vfมีชื่อเดียวกันรายการพารามิเตอร์ชนิด (8.3.5) คุณสมบัติ CV และคุณสมบัติผู้อ้างอิง ( หรือไม่มีเดียวกัน) ในขณะที่Base::vfมีการประกาศแล้วDerived::vfยังเป็นเสมือน ( หรือไม่ก็มีการประกาศเพื่อให้ ) Base::vfและมันจะแทนที่


5
นี่คือคำตอบที่ดีที่สุดที่นี่
ยอดนายฟ็อกซ์

33

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

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

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

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

คอมไพเลอร์จะออกข้อผิดพลาดในการคอมไพล์และข้อผิดพลาดในการเขียนโปรแกรมจะชัดเจนทันที (บางทีฟังก์ชันใน Derived ควรใช้floatเป็นอาร์กิวเมนต์)

อ้างถึงWP: C ++ 11


11

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


7

ไม่มีความแตกต่างสำหรับคอมไพเลอร์เมื่อคุณเขียนvirtualในคลาสที่ได้รับหรือละเว้นมัน

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


2

มีความแตกต่างอย่างมากเมื่อคุณมีแม่แบบและเริ่มรับคลาสฐานเป็นพารามิเตอร์แม่แบบ:

struct None {};

template<typename... Interfaces>
struct B : public Interfaces
{
    void hello() { ... }
};

struct A {
    virtual void hello() = 0;
};

template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
    b.hello();   // indirect, non-virtual call
}

void hello(const A& a)
{
    a.hello();   // Indirect virtual call, inlining is impossible in general
}

int main()
{
    B<None>  b;         // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
    B<None>* pb = &b;
    B<None>& rb = b;

    b.hello();          // direct call
    pb->hello();        // pb-relative non-virtual call (1 redirection)
    rb->hello();        // non-virtual call (1 redirection unless optimized out)
    t_hello(b);         // works as expected, one redirection
    // hello(b);        // compile-time error


    B<A>     ba;        // Ok, vtable generated, sizeof(b) >= sizeof(void*)
    B<None>* pba = &ba;
    B<None>& rba = ba;

    ba.hello();         // still can be a direct call, exact type of ba is deducible
    pba->hello();       // pba-relative virtual call (usually 3 redirections)
    rba->hello();       // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
    //t_hello(b);       // compile-time error (unless you add support for const A& in t_hello as well)
    hello(ba);
}

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

โปรดทราบว่าหากคุณทำเช่นนี้คุณอาจต้องการประกาศตัวสร้างสำเนา / ย้ายเป็นเทมเพลตเช่นกันการอนุญาตให้สร้างจากอินเทอร์เฟซที่แตกต่างกันช่วยให้คุณสามารถ 'ส่ง' ระหว่างB<>ประเภทที่แตกต่างกัน

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


1

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

struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};

ที่นี่CสืบทอดมาBดังนั้นจึงBไม่ใช่คลาสพื้นฐาน (เป็นคลาสที่ได้รับ) และCเป็นคลาสที่ได้รับ แผนภาพการสืบทอดมีลักษณะดังนี้:

A
^
|
B
^
|
C

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

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


0

แน่นอนฉันจะรวมคำหลักเสมือนจริงสำหรับชั้นเรียนของเด็กด้วยเพราะ

  • ผม. การอ่าน
  • ii คลาสลูกนี้ที่ฉันจะได้รับต่อไปลงคุณไม่ต้องการนวกรรมิกของคลาสที่ได้รับเพิ่มเติมที่จะเรียกฟังก์ชั่นเสมือนนี้

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