ฉันสามารถเรียกใช้ฟังก์ชันเสมือนของคลาสพื้นฐานได้หรือไม่ถ้าฉันลบล้างมัน?


340

ว่าฉันมีชั้นเรียนFooและBarตั้งค่าเช่นนี้:

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
        std::cout << x << std::endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        // I would like to call Foo.printStuff() here...
        std::cout << y << std::endl;
    }
};

ดังที่อธิบายไว้ในรหัสฉันต้องการที่จะสามารถเรียกฟังก์ชั่นระดับฐานที่ฉันเอาชนะ ใน Java มีsuper.funcname()ไวยากรณ์ เป็นไปได้ใน C ++?



1
สำหรับชาว Google: โปรดทราบว่าคุณอาจมีปัญหาอย่างที่ฉันทำเมื่อเก็บไว้เป็นตัวแปรสมาชิกระดับที่ไม่ใช่ตัวชี้ ดูคำตอบของฉันที่นี่: stackoverflow.com/questions/4798966/…ฉันมีส่วนเกี่ยวข้องใหม่ / ลบเพื่อแก้ไข
แอนดรู

คำตอบ:


449

ไวยากรณ์ C ++ เป็นดังนี้:

class Bar : public Foo {
  // ...

  void printStuff() {
    Foo::printStuff(); // calls base class' function
  }
};

11
มีปัญหาที่เป็นไปได้ในการทำเช่นนี้หรือไม่? มันเป็นการปฏิบัติที่ไม่ดี?
Robben_Ford_Fan_boy

36
@ David: ไม่เป็นเรื่องปกติที่จะทำสิ่งนี้แม้ว่ามันจะขึ้นอยู่กับชั้นเรียนจริงของคุณถ้ามันมีประโยชน์จริง ๆ เรียกวิธีการเรียนพื้นฐานเท่านั้นถ้าทำสิ่งที่คุณต้องการจะเกิดขึ้น;)
sth

20
ระวังนี่คือกลิ่นรหัสเมื่อลูกค้าของคุณจะต้องทำ! (เรียกcall super requirementดูได้ที่นี่: en.wikipedia.org/wiki/Call_super )
v.oddou

8
@ v.oddou: ไม่มีอะไรผิดปกติกับการเรียก super class เมื่อมันมีประโยชน์ หน้าเว็บที่คุณเชื่อมโยงนั้นเกี่ยวข้องกับปัญหาเฉพาะบางอย่างที่เกี่ยวข้องกับการสืบทอด มันยังบอกตัวเองว่าไม่มีอะไรผิดปกติกับการเรียก super class โดยทั่วไป: "โปรดทราบว่ามันเป็นความต้องการของการเรียก parent ที่เป็น anti-pattern มีตัวอย่างมากมายในโค้ดจริงที่เมธอดในคลาสย่อยอาจยังคงอยู่ ต้องการฟังก์ชั่นของซูเปอร์คลาสซึ่งโดยปกติจะเป็นแค่การเพิ่มฟังก์ชั่นหลักเท่านั้น "
sth

26
มันอาจเห็นได้ชัดที่สุด แต่สำหรับความสมบูรณ์อย่าลืมทำสิ่งนี้ใน constructor และ destructors
TigerCoding

123

ใช่,

class Bar : public Foo
{
    ...

    void printStuff()
    {
        Foo::printStuff();
    }
};

มันเหมือนกับsuperใน Java ยกเว้นจะอนุญาตให้เรียก implementations จากฐานต่าง ๆ เมื่อคุณมีหลายมรดก

class Foo {
public:
    virtual void foo() {
        ...
    }
};

class Baz {
public:
    virtual void foo() {
        ...
    }
};

class Bar : public Foo, public Baz {
public:
    virtual void foo() {
        // Choose one, or even call both if you need to.
        Foo::foo();
        Baz::foo();
    }
};

7
นี่เป็นคำตอบที่ดีกว่าคำตอบที่เลือก ขอบคุณ
นักฟิสิกส์บ้า

หลายมรดก โอ้โห!
lamino

69

บางครั้งคุณต้องเรียกใช้งานคลาสฐานเมื่อคุณไม่ได้อยู่ในฟังก์ชันที่ได้รับ ... มันยังใช้งานได้:

struct Base
{
    virtual int Foo()
    {
        return -1;
    }
};

struct Derived : public Base
{
    virtual int Foo()
    {
        return -2;
    }
};

int main(int argc, char* argv[])
{
    Base *x = new Derived;

    ASSERT(-2 == x->Foo());

    //syntax is trippy but it works
    ASSERT(-1 == x->Base::Foo());

    return 0;
}

2
โปรดทราบว่าคุณต้องมีทั้งxประเภทBase*และใช้Base::Fooไวยากรณ์เพื่อให้ทำงานได้
TTimo

27

ในกรณีที่คุณทำสิ่งนี้เพื่อฟังก์ชั่นมากมายในชั้นเรียนของคุณ:

class Foo {
public:
  virtual void f1() {
    // ...
  }
  virtual void f2() {
    // ...
  }
  //...
};

class Bar : public Foo {
private:
  typedef Foo super;
public:
  void f1() {
    super::f1();
  }
};

นี่อาจช่วยประหยัดการเขียนเล็กน้อยหากคุณต้องการเปลี่ยนชื่อ Foo


1
วิธีที่สนุกที่จะทำ แต่จะไม่ทำงานกับหลายมรดก
นักฟิสิกส์บ้า

5
และถ้าคุณมีคลาสพื้นฐานมากกว่า 1 คลาสล่ะ? อย่างไรก็ตามมันโง่และไร้ประโยชน์บ่อยครั้งที่พยายามงอ C ++ ให้ดูเหมือนภาษาอื่น เพียงจำชื่อฐานและเรียกมันโดยที่
underscore_d

6

ถ้าคุณต้องการเรียกใช้ฟังก์ชันคลาสพื้นฐานจากคลาสที่ได้รับคุณสามารถโทรภายในฟังก์ชันแทนที่ด้วยชื่อคลาสพื้นฐาน (เช่นFoo :: printStuff () )

รหัสไปที่นี่

#include <iostream>
using namespace std;

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
         cout<<"Base Foo printStuff called"<<endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        cout<<"derived Bar printStuff called"<<endl;
        Foo::printStuff();/////also called the base class method
    }
};

int main()
{
    Bar *b=new Bar;
    b->printStuff();
}

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

รหัสด้านล่าง

#include <iostream>
using namespace std;

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
         cout<<"Base Foo printStuff called"<<endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        cout<<"derived Bar printStuff called"<<endl;
    }
};

int main()
{

    Foo *foo=new Foo;
    foo->printStuff();/////this call the base function
    foo=new Bar;
    foo->printStuff();
}

0

ตรวจสอบสิ่งนี้ ...

#include <stdio.h>

class Base {
public:
   virtual void gogo(int a) { printf(" Base :: gogo (int) \n"); };    
   virtual void gogo1(int a) { printf(" Base :: gogo1 (int) \n"); };
   void gogo2(int a) { printf(" Base :: gogo2 (int) \n"); };    
   void gogo3(int a) { printf(" Base :: gogo3 (int) \n"); };
};

class Derived : protected Base {
public:
   virtual void gogo(int a) { printf(" Derived :: gogo (int) \n"); };
   void gogo1(int a) { printf(" Derived :: gogo1 (int) \n"); };
   virtual void gogo2(int a) { printf(" Derived :: gogo2 (int) \n"); };
   void gogo3(int a) { printf(" Derived :: gogo3 (int) \n"); };       
};

int main() {
   std::cout << "Derived" << std::endl;
   auto obj = new Derived ;
   obj->gogo(7);
   obj->gogo1(7);
   obj->gogo2(7);
   obj->gogo3(7);
   std::cout << "Base" << std::endl;
   auto base = (Base*)obj;
   base->gogo(7);
   base->gogo1(7);
   base->gogo2(7);
   base->gogo3(7);

   std::string s;
   std::cout << "press any key to exit" << std::endl;
   std::cin >> s;
   return 0;
}

เอาท์พุต

Derived
 Derived :: gogo (int)
 Derived :: gogo1 (int)
 Derived :: gogo2 (int)
 Derived :: gogo3 (int)
Base
 Derived :: gogo (int)
 Derived :: gogo1 (int)
 Base :: gogo2 (int)
 Base :: gogo3 (int)
press any key to exit

วิธีที่ดีที่สุดคือการใช้ฟังก์ชั่นฐาน ::ตามที่พูด @sth


ตามที่อธิบายไว้ในคำถามนี้สิ่งนี้ไม่ควรได้ผลเพราะการprotectedสืบทอด ในการใช้งานตัวชี้คลาสพื้นฐานคุณต้องใช้การสืบทอดสาธารณะ
Darkproduct

น่าสนใจ หลังจากอ่านคำตอบนี้ฉันคิดว่ามรดกที่ได้รับความคุ้มครองความจริงที่ว่า Derived มาจากฐานจะมองเห็นได้เฉพาะชั้นเรียนเท่านั้นและไม่สามารถมองเห็นจากภายนอกได้เช่นกัน
Darkproduct

0

ใช่คุณสามารถเรียกมันว่า ไวยากรณ์ C ++ สำหรับการเรียกฟังก์ชั่นระดับผู้ปกครองในชั้นเด็ก

class child: public parent {
  // ...

  void methodName() {
    parent::methodName(); // calls Parent class' function
  }
};

อ่านเพิ่มเติมเกี่ยวกับฟังก์ชั่นที่เอาชนะ

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