แทนที่ฟังก์ชันเสมือน C ++ อย่างปลอดภัย


100

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

ใช้ตัวอย่างนี้:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

ที่นี่parent::handle_event()เรียกว่าแทนchild::handle_event()เนื่องจากวิธีการของเด็กพลาดการconstประกาศจึงประกาศวิธีการใหม่ นี่อาจเป็นการพิมพ์ผิดในชื่อฟังก์ชันหรือความแตกต่างเล็กน้อยในประเภทพารามิเตอร์ นอกจากนี้ยังสามารถเกิดขึ้นได้อย่างง่ายดายหากอินเทอร์เฟซของคลาสพื้นฐานเปลี่ยนไปและบางคลาสที่ได้รับมาไม่ได้รับการอัปเดตเพื่อให้สอดคล้องกับการเปลี่ยนแปลง

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


2
คำถามที่ดีฉันพยายามหาคำตอบว่าทำไมฟังก์ชันคลาสลูกของฉันถึงไม่ถูกเรียกมาตั้งแต่หนึ่งชั่วโมงแล้ว!
Akash Mankar

คำตอบ:


89

เนื่องจาก g ++ 4.7 เข้าใจoverrideคำหลักC ++ 11 ใหม่:

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz: ฉันพบว่าเมื่อคุณใช้ฟังก์ชัน handle_event และคุณผนวกการแทนที่เมื่อสิ้นสุดการใช้ฟังก์ชัน g ++ จะให้ข้อผิดพลาด หากคุณจัดเตรียมการใช้งานฟังก์ชันแบบอินไลน์ในการประกาศคลาสตามคีย์เวิร์ดแทนที่ทุกอย่างก็เรียบร้อยดี ทำไม?
h9uest

3
overrideต้องใช้@ h9uest ในคำจำกัดความ การใช้งานแบบอินไลน์เป็นทั้งคำจำกัดความและการนำไปใช้งานดังนั้นก็โอเค
Gunther Piez

@hirschhornsalz ใช่ฉันได้รับข้อความแสดงข้อผิดพลาดเดียวกันโดย g ++ หมายเหตุด้านข้าง: ทั้งคุณและข้อความแสดงข้อผิดพลาด g ++ ใช้คำว่า "class definition" - เราไม่ควรใช้ "declaration" (คู่ {declaration, definition}) หรือไม่ คุณทำให้ตัวเองชัดเจนในบริบทนี้โดยพูดว่า "คำจำกัดความและการนำไปใช้" แต่ฉันแค่สงสัยว่าทำไมชุมชน c ++ ถึงตัดสินใจเปลี่ยนเงื่อนไขในชั้นเรียนอย่างกะทันหัน?
h9uest

20

บางอย่างเช่นoverrideคำหลักของ C # ไม่ได้เป็นส่วนหนึ่งของ C ++

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


2
ถ้าคุณใช้ Visual C ++
Steve Rowe

4
การใช้ Visual C ++ ไม่ได้สร้างoverrideคีย์เวิร์ดใน C ++ อาจหมายความว่าคุณกำลังใช้บางสิ่งที่สามารถรวบรวมซอร์สโค้ด C ++ ที่ไม่ถูกต้องได้ ;)
CB Bailey

3
ความจริงที่ว่าการแทนที่ไม่ถูกต้อง C ++ หมายความว่ามาตรฐานนั้นไม่ถูกต้องไม่ใช่ Visual C ++
จอน

2
@ จอน: ตกลงตอนนี้ฉันเห็นสิ่งที่คุณขับรถอยู่ โดยส่วนตัวแล้วฉันสามารถใช้หรือออกจากoverrideฟังก์ชันรูปแบบ C # ได้ ฉันไม่ค่อยมีปัญหากับการลบล้างที่ล้มเหลวและการวินิจฉัยและแก้ไขนั้นค่อนข้างง่าย สิ่งหนึ่งที่ฉันไม่เห็นด้วยคือผู้ใช้ VC ++ ควรใช้ ฉันต้องการให้ C ++ ดูเหมือน C ++ ในทุกแพลตฟอร์มแม้ว่าโครงการใดโครงการหนึ่งจะไม่จำเป็นต้องพกพาก็ตาม มันน่าสังเกตว่า C ++ 0x จะมี[[base_check]], [[override]]และ[[hiding]]แอตทริบิวต์เพื่อให้คุณสามารถเลือกที่จะตรวจสอบการแทนที่ถ้าต้องการ
CB Bailey

5
ขันพอสองสามปีหลังจากความคิดเห็นที่โดยใช้ VC ++ ไม่ได้ทำให้overrideคำหลักที่ดูเหมือนว่ามันไม่ได้ ไม่ใช่คำหลักที่เหมาะสม แต่เป็นตัวระบุพิเศษใน C ++ 11 Microsoft ผลักดันอย่างหนักพอที่จะสร้างกรณีพิเศษนี้และทำตามรูปแบบทั่วไปสำหรับแอตทริบิวต์และoverrideทำให้เป็นมาตรฐาน :)
David Rodríguez - dribeas

18

เท่าที่ฉันรู้คุณไม่สามารถทำให้เป็นนามธรรมได้หรือไม่?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

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


ตอนนี้ฉันรู้แล้วว่ามันใช้งานได้มากกว่าผู้ทำลายล้าง! การค้นพบที่ยอดเยี่ยม
strager

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

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

นอกจากนี้ยังป้องกันวิธีพื้นฐาน (พูดBaseClass::method()) ที่จะถูกเรียกในการนำไปใช้งานที่ได้รับ (พูดDerivedClass::method()) เช่นสำหรับค่าเริ่มต้น
Narcolessico

11

ใน MSVC คุณสามารถใช้overrideคีย์เวิร์ดCLR แม้ว่าคุณจะไม่ได้รวบรวม CLR ก็ตาม

ใน g ++ ไม่มีวิธีบังคับโดยตรงในทุกกรณี คนอื่น ๆ -Woverloaded-virtualได้รับคำตอบที่ดีเกี่ยวกับวิธีการที่จะจับความแตกต่างโดยใช้ลายเซ็น ในเวอร์ชันอนาคตอาจมีคนเพิ่มไวยากรณ์ที่เหมือน__attribute__ ((override))หรือเทียบเท่าโดยใช้ไวยากรณ์ C ++ 0x



5

ทำให้ฟังก์ชันเป็นนามธรรมเพื่อให้คลาสที่ได้รับมาไม่มีทางเลือกอื่นนอกจากแทนที่มัน

@Ray รหัสของคุณไม่ถูกต้อง

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

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

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

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

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

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


2

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

ตัวอย่างเช่นนี่คือคำเตือน C4263 ใน Microsoft Visual C ++


1

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

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

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.