Як викликати функцію батьківського класу з похідної функції класу?


602

Як я можу викликати батьківську функцію з похідного класу за допомогою C ++? Наприклад, у мене є клас, який називається parent, і клас, childякий називається, походить від батьківського. Всередині кожного класу є printфункція. У визначенні функції друку дитини я хотів би зателефонувати до функції друку батьків. Як би я пішов робити це?


Усі вищезазначені рішення припускають, що функція друку є статичним методом. Це так? Якщо метод не є статичним, наведені вище рішення не мають значення.
hhafez

14
hhafez, ви помиляєтеся на синтаксис base :: function (), схожий на синтаксис статичного виклику статичного методу, але він працює, наприклад, у цьому контексті.
Мотті

2
Я б не використовував MSVC __super, оскільки це специфічно для платформи. Хоча ваш код може не працювати на будь-якій іншій платформі, я б використав інші пропозиції, оскільки вони роблять це відповідно до мови.
Тізер


Антипатерн, де похідні класи завжди потрібні для виклику функцій батьківського класу, - Call super
Руфус

Відповіді:


774

Я ризикую висловити очевидне: Ви викликаєте функцію, якщо вона визначена в базовому класі, вона автоматично доступна у похідному класі (якщо вона не є private).

Якщо у похідному класі є функція з однаковою підписом, ви можете її відключити, додавши ім'я базового класу, а потім два двокрапки base_class::foo(...). Слід зазначити, що на відміну від Java та C #, C ++ не має ключового слова для "базового класу" ( superабо base), оскільки C ++ підтримує багатократне успадкування, що може призвести до неоднозначності.

class left {
public:
    void foo();
};

class right {
public:
    void foo();
};

class bottom : public left, public right {
public:
    void foo()
    {
        //base::foo();// ambiguous
        left::foo();
        right::foo();

        // and when foo() is not called for 'this':
        bottom b;
        b.left::foo();  // calls b.foo() from 'left'
        b.right::foo();  // call b.foo() from 'right'
    }
};

Між іншим, ви не можете виводити безпосередньо з одного класу двічі, оскільки не буде можливості посилатися на один із базових класів над іншим.

class bottom : public left, public left { // Illegal
};

30
Чому ви хотіли б успадкувати від одного класу двічі?
Пол Брючинський

65
@bluesm: в класичному OOP це не має великого сенсу, але в загальному програмуванні template<class A, class B> class C: public A, public B {};можна привести два типи, однакові з причин залежно від того, як використовується ваш код (завдяки чому A і B будуть однаковими), може бути два-три абстракційний шар - шлях від того, хто не знає, що ти зробив.
Еміліо Гаравалья

8
Я думаю , що це корисно додати, що це буде викликати батьківський метод класу , навіть якщо вона не реалізується безпосередньо в батьківському класі, але буде реалізований в одному з батьківських класів в ланцюжку спадкування.
Максим Лавров

4
Щось із стороною, це мене зле з розуму, коли я намагався помістити це у файл cpp. У мене було "використання простору імен std". 'зліва' визначено десь у цьому просторі імен. Приклад не збирався - зводив мене з розуму :). Потім я змінив "ліворуч" на "Ліворуч". Чудовий приклад до речі.
Матей

72
@Mathai І саме тому ви не повинні використовувати using namespace std.
JAB

193

Враховуючи названий батьківський клас Parentта ім'я дочірнього класу Child, ви можете зробити щось подібне:

class Parent {
public:
    virtual void print(int x);
}

class Child : public Parent {
    void print(int x) override;
}

void Parent::print(int x) {
    // some default behavior
}

void Child::print(int x) {
    // use Parent's print method; implicitly passes 'this' to Parent::print
    Parent::print(x);
}

Зауважте, що Parentце власне назва класу, а не ключове слово.


Звичайно, це було б корисно лише в тому випадку, якщо базовий виклик буде перемежований з іншою логікою, інакше не буде сенсу переосмислювати функцію, тож, можливо, це занадто
underscore_d

1
@underscore_d насправді, його корисно навіть у тому випадку, якщо базовий виклик не змішувався з іншою логікою. Скажімо, батьківський клас в значній мірі робить все, що завгодно, але викриває метод foo (), який ви не хочете, щоб користувачі дитини використовували - або тому, що foo () є безглуздим для дитини, або зовнішні абоненти, щоб дитина викручує, що таке дитина робити. Таким чином, дитина може використовувати parent: foo () у певних ситуаціях, але забезпечувати реалізацію foo, щоб вони приховували батьківський foo () від виклику.
iheanyi

@iheanyi Звучить цікаво, але вибачте, я ще не розумію цього. Є foo()тут аналогічна print()або окремої функції? І ви маєте в виду, використовуючи privateспадок приховати подробиці , успадковане від підстави, а також надання publicзатінення функції для речей , які ви дійсно хочете , щоб виставити?
підкреслюй_d

@underscore_d Так, це foo()було аналогічно print(). Дозвольте повернутися до використання, print()як я думаю, що це мало б більше сенсу в цьому контексті. Скажімо, хтось створив клас, який здійснив певний набір операцій над певним типом даних, викрив деякі аксесуари та мав print(obj&)метод. Мені потрібен новий клас, над яким працює, array-of-objале все інше те саме. Композиція призводить до багато дублювання коду. Спадщина мінімізує це при print(array-of-obj&) циклічному виклику print(obj&), але не хоче, щоб клієнти дзвонили, print(obj&)тому що не має сенсу робити це
iheanyi

@underscore_d Це ґрунтується на припущенні, що я не можу переробляти загальні частини початкового батьківського класу, або що це надзвичайно дорого. Приватне успадкування могло б спрацювати, але тоді ви втрачаєте публічні користувачі, на які ви покладалися - і, таким чином, потрібно буде дублювати код.
iheanyi

32

Якщо ваш базовий клас викликається Base, а ваша функція викликається, FooBar()ви можете викликати його безпосередньо, використовуючиBase::FooBar()

void Base::FooBar()
{
   printf("in Base\n");
}

void ChildOfBase::FooBar()
{
  Base::FooBar();
}

28

У MSVC є ключове ключове слово Microsoft для цього: __super


MSDN: Дозволяє чітко вказати, що ви викликаєте реалізацію базового класу для функції, яку ви переосмислюєте.

// deriv_super.cpp
// compile with: /c
struct B1 {
   void mf(int) {}
};

struct B2 {
   void mf(short) {}

   void mf(char) {}
};

struct D : B1, B2 {
   void mf(short) {
      __super::mf(1);   // Calls B1::mf(int)
      __super::mf('s');   // Calls B2::mf(char)
   }
};


5
Е, я вважаю typdefза краще батько як щось подібне super.
Томас Едінг

26
Я не намагаюся виправдати використання __super; Я згадував це тут як альтернативну пропозицію. Розробники повинні знати свого компілятора і розуміти плюси і мінуси його можливостей.
Андрій

13
Я вважаю за краще відмовити когось від його використання, оскільки це сильно заважає переносимості коду.
Ербурет говорить, що поверніть Моніку

26
Я не погоджуюся з Андрієм: Розробники повинні знати стандарт і не повинні турбуватися з можливостями компілятора, якщо ми вважаємо, що написання програмного забезпечення, яке в першу чергу є незалежним від компілятора, я думаю, що це все-таки хороша ідея, оскільки рано чи пізно у великих проектах кілька компіляторів будь-коли використовуються.
Габріель

7
"Розробники повинні знати свого компілятора", це міркування та включення нестандартних функцій - це те, що призвело до IE6 ...
e2-e4

5

Якщо модифікатор доступу базового класу функції захищений АБО загальнодоступним, ви можете виконати функцію члена виклику базового класу з похідного класу. Викликати функцію базового класу невіртуальну та віртуальну учасницю можна з похідної функції члена. Будь ласка, зверніться до програми.

#include<iostream>
using namespace std;

class Parent
{
  protected:
    virtual void fun(int i)
    {
      cout<<"Parent::fun functionality write here"<<endl;
    }
    void fun1(int i)
    {
      cout<<"Parent::fun1 functionality write here"<<endl;
    }
    void fun2()
    {

      cout<<"Parent::fun3 functionality write here"<<endl;
    }

};

class Child:public Parent
{
  public:
    virtual void fun(int i)
    {
      cout<<"Child::fun partial functionality write here"<<endl;
      Parent::fun(++i);
      Parent::fun2();
    }
    void fun1(int i)
    {
      cout<<"Child::fun1 partial functionality write here"<<endl;
      Parent::fun1(++i);
    }

};
int main()
{
   Child d1;
   d1.fun(1);
   d1.fun1(2);
   return 0;
}

Вихід:

$ g++ base_function_call_from_derived.cpp
$ ./a.out 
Child::fun partial functionality write here
Parent::fun functionality write here
Parent::fun3 functionality write here
Child::fun1 partial functionality write here
Parent::fun1 functionality write here

1
Дякуємо, що навели кілька прикладів virtual!
М.Іоан

5

Виклик батьківського методу з оператором розв’язання батьківського діапазону.

Батько :: метод ()

class Primate {
public:
    void whatAmI(){
        cout << "I am of Primate order";
    }
};

class Human : public Primate{
public:
    void whatAmI(){
        cout << "I am of Human species";
    }
    void whatIsMyOrder(){
        Primate::whatAmI(); // <-- SCOPE RESOLUTION OPERATOR
    }
};

-15
struct a{
 int x;

 struct son{
  a* _parent;
  void test(){
   _parent->x=1; //success
  }
 }_son;

 }_a;

int main(){
 _a._son._parent=&_a;
 _a._son.test();
}

Довідковий приклад.


2
Чи можете ви редагувати пояснення, чому / як цей код відповідає на питання? Відповіді, що стосуються лише коду, відлякують, оскільки їх не так просто вивчити, як код з поясненням. Без пояснення потрібно значно більше часу та зусиль, щоб зрозуміти, що робиться, зміни, внесені до коду, чи корисний код. Пояснення важливе як для людей, які намагаються дізнатись з відповіді, так і тих, хто оцінює відповідь, щоб побачити, чи справді вона, чи варто голосувати.
Макіен

3
Ця відповідь стосується вкладених класів, тоді як питання стосується похідних класів (хоча слова "батько" та "дитина" є дещо оманливими), а отже, зовсім не відповідає на це питання.
Йоганнес Матокич
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.