[C++] Как из метода прозводного класса вызвать метод базового класса с тем же именем?
Что-то не могу понять, можно ли такое провернуть в C++.
Есть базовый класс, и производный от него. В базовом классе есть некий метод (не чисто виртуальный, а с кодом). Пускай он называется action() к примеру.
Метод action() переопределяется в производном классе. И вот стоит задача в методе action() производного класса вызвать метод action() базового класса.
То есть нужно сделать так, чтобы действия, выполняемые в производном классе, не «заменяли» действия базового класса, а «дополняли».
Вопрос — возможно ли так сделать в C++? Если возможно, то как?
Вызов методов базового класса
Надо вызвать метод базового класса из метода, который переопределен в производном классе.
Из конструктора дочернего класса нужно явно вызывать конструктор родительского класса.
Обращение к базовому классу происходит с помощью super()
Нужно явно вызывать конструктор базового класса
class A(object): def __init__(self, x=5): print('A.__init__') self.x = x class B(A): def __init__(self, y=2): print('B.__init__') self.y = y k = B(7) # B.__init__ print('k.y =', k.y) # k.y = 7 #print('k.x =', k.x) # AttributeError: 'B' object has no attribute 'x'
Видно, что без явного вызова конструктора класса А не вызывается A.__init__ и не создается поле x класса А.
Вызовем конструктор явно.
Конструктор базового класса стоит вызывать раньше, чем иницилизировать поля класса-наследника, потому что поля наследника могут зависеть (быть сделаны из) полей экземпляра базового класса.
class A(object): def __init__(self, x=5): print('A.__init__') self.x = x class B(A): def __init__(self, y=2): print('B.__init__') super().__init__(y/2) self.y = y k = B(7) # B.__init__ # A.__init__ print('k.y =', k.y) # k.y = 7 print('k.x =', k.x) # k.x = 3.5
super() или прямое обращение к классу?
Метод класса можно вызвать, используя синтаксис вызова через имя класса:
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') k = A() # Base.__init__ # A.__init__
Все работает. Но при дальнейшем развитии классов могут начаться проблемы:
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') class B(Base): def __init__(self): Base.__init__(self) print('B.__init__') class C(A, B): def __init__(self): A.__init__(self) B.__init__(self) print('C.__init__') x = C() # Base.__init__ # A.__init__ # Base.__init__ - второй вызов # B.__init__ # C.__init__
Видно, что конструктор Base.__init__ вызывается дважды. Иногда это недопустимо (считаем количество созданных экземпляров класса, увеличивая в конструкторе счетчик на 1; выдаем очередное auto id какому-то нашему объекту, например, номер пропуска или паспорта или номер заказа).
То же самое через super():
- вызов конструктора Base.__init__ происходит только 1 раз.
- вызваны конструкторы всех базовых классов.
- порядок вызова конструкторов для классов А и В не определен.
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): super().__init__() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') x = C() # Base.__init__ # B.__init__ - вызваны конструкторы обоих базовых классов # A.__init__ - порядок вызова # C.__init__
Как это работает?
Для реализации наследования питон ищет вызванный атрибут начиная с первого класса до последнего. Этот список создается слиянием (merge sort) списков базовых классов:
- дети проверяются раньше родителей.
- если родителей несколько, то проверяем в том порядке, в котором они перечислены.
- если подходят несколько классов, то выбираем первого родителя.
При вызове super() продолжается поиск, начиная со следующего имени в MRO. Пока каждый переопределенный метод вызывает super() и вызывает его только один раз, будет перебран весь список MRO и каждый метод будет вызван только один раз.
Не забываем вызывать метод суперкласса
А если где-то не вызван метод суперкласса?
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): #super().__init__() - НЕ вызываем super() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') x = C() # A.__init__ # C.__init__ print(C.__mro__) # (, , , , )
Заметим, что хотя в B.__init__ есть вызов super(), то до вызова B.__init__ не доходит.
- Вызываем у объекта класса С метод __init__.
- Ищем его в mro и находим С.__init__. Выполняем его.
- В этом методе вызов super() — ищем метод __init__ далее по списку от найденного.
- Находим A.__init__. Выполняем его. В нем нет никаких super() — дальнейший поиск по mro прекращается.
Нет метода в своем базовом классе, есть у родителя моего сиблинга
Определим класс, который пытается вызвать метод, которого нет в базовом классе:
class A(object): def spam(self): print('A.spam') super().spam() x = A() x.spam()
получим, как и ожидалось:
A.spam Traceback (most recent call last): File "examples/oop_super_3.py", line 7, in x.spam() File "examples/oop_super_3.py", line 4, in spam super().spam() AttributeError: 'super' object has no attribute 'spam'
Определим метод spam в классе В. Класс С, наследник А и В, вызывает метод A.spam(), который вызывает B.spam — класс В не связан с классом А.
class A(object): def spam(self): print('A.spam') super().spam() class B(object): def spam(self): print('B.spam') class C(A, B): pass y = C() y.spam() print(C.__mro__)
A.spam B.spam (, , , )
Для объекта класса С вызвали метод spam(). Ищем его в MRO. Находим A.spam() и вызываем. Далее для super() из A.spam() идем дальше от найденного по списку mro и находим B.spam().
Отметим, что при другом порядке описания родителей class C(B, A) , вызывается метод B.spam() у которого нет super():
B.spam (, , , )
Вызываем метод spam для объекта класса С. В С его нет, ищем дальше в В. Находим. Вызваем. Далее super() нет и дальнейший поиск не производится.
Чтобы не было таких сюрпризов при переопределении методов придерживайтесь правил:
- все методы в иерархии с одинаковым именем имеют одинаковую сигнатуру вызова (количество аргументов и их имена для именованных аргументов).
- реализуйте метод в самом базовом классе, чтобы цепочка вызовов закончилась хоть каким
Обращение к дедушке
Игнорируем родителя
Если у нас есть 3 одинаковых метода foo(self) в наследуемых классах А, В(А), C(B), и нужно из C.foo() вызвать сразу A.foo() минуя B.foo(), то наши классы неправильно сконструированы (почему нужно игнорировать В? может, нужно было наследовать С от А, а не от В?). Нужен рефакторинг.
Но можно всегда вызвать метод по имени класса:
class A(object): def spam(self): print('A.spam') class B(A): def spam(self): print('B.spam') class C(B): def spam(self): A.spam(self) print('C.spam') y = C() y.spam() print(C.__mro__)
A.spam C.spam (, , , )
Метод определен только у дедушки
Если в В такого метода нет, и из C.foo() нужно вызвать A.foo() (или в базовом классе выше по иерархии), вызываем super().foo() и больше не думаем, у какого пра-пра-пра-дедушки реализован этот метод.
Просто воспользуйтесь super() для поиска по mro.
class A(object): def spam(self): print('A.spam') class B(A): pass class C(B): def spam(self): super().spam() print('C.spam') y = C() y.spam() print(C.__mro__)
A.spam C.spam (, , , )
super().super() не работает
Или мы ищем какого-то родителя в mro, или точно указываем из какого класса нужно вызвать метод.
Литература
- Документация по python
- Python’s super() considered super!
- Python Cookbook, chapter 8.7 Calling a Method on a Parent Class
Как вызвать функцию класса из переменной?
Подскажите пожалуйста, каким образом, можно в C++ сохранить функцию класса в переменную, и затем вызвать её из основного тела программы? Если делать всё просто в main, без разделение на классы, то всё работает. Я же хочу сделать именно в разных файлах.
Как бы я не пытался придумать, у меня выходит ошибка:
non-standard syntax; use '&' to create a pointer to member
Пробую как-то так:
//Class.h: class Class < public: typedef void(Class::*funcType)(); funcType func; void test(); >; //Class.cpp: #include "Class.h" #include Class::Class() < func = Class::test; >void Class::test() < std::cout //Program.cpp #include #include "Class.h" void mian()
Отслеживать
задан 5 июн 2019 в 11:21
3 1 1 бронзовый знак
К тому же указатель на метод немного отличается от указателя на функцию. Ему нужен объект, к которому этот указатель на метод мы можем применить. И тогда получится такая штука (c.*c.func)();
5 июн 2019 в 11:49
Пробовал ставить * и & в различных комбинациях, это не изменяло ошибку. Что-то не пойму, как использовать (c.*c.func)
Как правильно вызвать функцию другого класса?
Очень не советую использовать callback-и (да и большую часть других плюшек С++ последних стандартов) раньше, чем научитесь обходиться без них. Передавайте объекты, используйте более традиционные методы. Когда сможете написать все без callback-ов и потратите на отладку этого кода меньше, чем на написание - тогда можно смело с ними знакомиться и использовать. Начнете с них - превратите свой код в минное поле и не сможете понять, где рвануло и почему.
Решения вопроса 2
Это называется функция обратного вызова, или Сallback
Существует достаточно много способов для реализации коллбеков, но самым современным решением в C++ является функтор.
Функтор, или функциональный объект - это просто класс с перезагруженным оператором () . Стандартная библиотека уже включает в себя интерфейс для удобного создания и использования функторов - std::function
#include void deleteAllItems(int); // foo; //
Ответ написан более трёх лет назад
Нравится 2 2 комментария
Евгений Шатунов @MarkusD Куратор тега C++
Pavel Tananykhin , это ответ на твой вопрос. 🙂
Так же советую использовать библиотеку libsigc++, которая по своей сути является отщеплением и развитием Boost signals. Она простая в освоении, удобная в использовании и быстрая в работе.
libsigc.sourceforge.net/index.shtml
https://developer.gnome.org/libsigc++-tutorial/stable/
Евгений Шатунов @MarkusD Куратор тега C++
А. кстати. Еще неплохо будет добавить пример использования функторов для вызова this-call функций.
Пример использования.
cpp.sh/2fuxm
"I'm here to consult you" © Dogbert
В другом классе я создаю указатель для этой функции.
void(InventoryContainer::*fnDelete)(unsigned int);
Но при попытке вызвать эту функцию,
this->fnDelete(this->IFrom);
студия показывает мне что this в данном случае не корректен.
1. Как правильно вызвать функцию?
Правильно показывает. fnDelete должен быть вызван для объекта класса InventoryContainer, и синтаксис разыменования указателя на функцию-член малость другой:
InventoryContainer *that; . (that->*fnDelete)(IFrom);
2. Где то на StackOverflow видел,что для вызова такой функции нужен ещё объект.Не лучше ли тогда просто передать ссылку на объект и через него и вызвать функцию?
Смысл указателя на функцию-член в том, что он даёт возможность вызывать любую функцию с такой сигнатурой в данном объекте. Т.е. для динамического выбора:
class A < public: void f1(); void f2(); >; void (A::*f)(); if (. ) f = &A::f1; else f = &A::f2; . A *pa; . (pa->*f)(); // вызывает f1 или f2